diff --git a/CHANGELOG.md b/CHANGELOG.md index d85406f477f100ac6b522edf4af7bbbc220df6bf..c02dfd9c1cf415b26594bde0d5a82c8d4345d4c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ This is a changelog for Piwik platform developers. All changes for our HTTP API' ### Breaking Changes * Some duplicate reports from UserSettings plugin have been removed. Widget URLs for those reports will still work till May 1st 2015. Please update those to the new reports of DevicesDetection plugin. +* The following events have been removed: + * `Log.formatFileMessage` + * `Log.formatDatabaseMessage` + * `Log.formatScreenMessage` + * These events where very specific events for an internal need (logging exceptions) and have been replaced by a more extensible solution. ### Deprecations * The API method `UserSettings.getBrowserVersion` is deprecated and will be removed from May 1st 2015. Use `DevicesDetection.getBrowserVersions` instead diff --git a/core/Error.php b/core/Error.php index 54d575d8816f6b1bb3ea55ce5a5a7d86bca13e36..33b53132d92edc2ec7fe41ba9bc1a78df00b00d7 100644 --- a/core/Error.php +++ b/core/Error.php @@ -8,9 +8,6 @@ */ namespace Piwik; -use Piwik\Log\Formatter\ErrorHtmlFormatter; -use Piwik\Log\Formatter\ErrorTextFormatter; - require_once PIWIK_INCLUDE_PATH . '/core/Log.php'; /** @@ -123,24 +120,8 @@ class Error } } - public static function formatFileAndDBLogMessage(&$message, $level, $tag, $datetime, Log $log) - { - $formatter = new ErrorTextFormatter(); - $message = $formatter->format($message, $level, $tag, $datetime, $log); - } - - public static function formatScreenMessage(&$message, $level, $tag, $datetime, $log) - { - $formatter = new ErrorHtmlFormatter(); - $message = $formatter->format($message, $level, $tag, $datetime, $log); - } - public static function setErrorHandler() { - Piwik::addAction('Log.formatFileMessage', array('Piwik\Error', 'formatFileAndDBLogMessage')); - Piwik::addAction('Log.formatDatabaseMessage', array('Piwik\Error', 'formatFileAndDBLogMessage')); - Piwik::addAction('Log.formatScreenMessage', array('Piwik\Error', 'formatScreenMessage')); - set_error_handler(array('Piwik\Error', 'errorHandler')); } diff --git a/core/ExceptionHandler.php b/core/ExceptionHandler.php index dfadcc27630ae86cbeb733119f44bccbc7d58f99..9bbb5a23acb94875f0e9984632f954ccf32bcd75 100644 --- a/core/ExceptionHandler.php +++ b/core/ExceptionHandler.php @@ -8,10 +8,6 @@ */ namespace Piwik; -use Piwik\Log\Formatter\ExceptionHtmlFormatter; -use Piwik\Log\Formatter\ExceptionTextFormatter; -use Piwik\Plugin; - /** * Contains Piwik's uncaught exception handler and log file formatting for exception * instances. @@ -27,25 +23,9 @@ class ExceptionHandler public static function setUp() { - Piwik::addAction('Log.formatFileMessage', array('Piwik\ExceptionHandler', 'formatFileAndDBLogMessage')); - Piwik::addAction('Log.formatDatabaseMessage', array('Piwik\ExceptionHandler', 'formatFileAndDBLogMessage')); - Piwik::addAction('Log.formatScreenMessage', array('Piwik\ExceptionHandler', 'formatScreenMessage')); - set_exception_handler(array('Piwik\ExceptionHandler', 'logException')); } - public static function formatFileAndDBLogMessage(&$message, $level, $tag, $datetime, Log $log) - { - $formatter = new ExceptionTextFormatter(); - $message = $formatter->format($message, $level, $tag, $datetime, $log); - } - - public static function formatScreenMessage(&$message, $level, $tag, $datetime, $log) - { - $formatter = new ExceptionHtmlFormatter(); - $message = $formatter->format($message, $level, $tag, $datetime, $log); - } - public static function logException(\Exception $exception) { Log::error($exception); diff --git a/core/Log.php b/core/Log.php index 52123d3593a3f97be1205d0ced42dd593074ed98..2892c4406d9bd2e01fb86864f9e17e689f3d2c3b 100644 --- a/core/Log.php +++ b/core/Log.php @@ -10,7 +10,8 @@ namespace Piwik; use Piwik\Container\StaticContainer; use Piwik\Db; -use Piwik\Log\Backend\ScreenBackend; +use Piwik\Log\Backend\StdOutBackend; +use Piwik\Log\LoggerFactory; /** * Logging utility class. @@ -308,16 +309,6 @@ class Log extends Singleton foreach ($this->writers as $writer) { call_user_func($writer, $level, $tag, $datetime, $message, $this); } - - // TODO this hack should be removed - if ($level == self::ERROR) { - $screenBackend = new ScreenBackend($this->logMessageFormat); - $message = $screenBackend->getMessageFormattedScreen($level, $tag, $datetime, $message, $this); - $this->writeErrorToStandardErrorOutput($message); - if (!isset($this->writers['screen'])) { - echo $message; - } - } } private static function logMessage($level, $message, $sprintfParams) @@ -357,19 +348,6 @@ class Log extends Singleton return false; } - /** - * @param $message - */ - private function writeErrorToStandardErrorOutput($message) - { - if (defined('PIWIK_TEST_MODE')) { - // do not log on stderr during tests (prevent display of errors in CI output) - return; - } - $fe = fopen('php://stderr', 'w'); - fwrite($fe, $message); - } - /** * Returns the name of the plugin/class that triggered the log. * diff --git a/core/Log/Backend/Backend.php b/core/Log/Backend/Backend.php index 6c3e64545c5325f432657198488d6ca3890b4d88..e5e77753ded910547c886575979a08f185f3c625 100644 --- a/core/Log/Backend/Backend.php +++ b/core/Log/Backend/Backend.php @@ -9,6 +9,7 @@ namespace Piwik\Log\Backend; use Piwik\Log; +use Piwik\Log\Formatter\Formatter; /** * Log backend. @@ -16,41 +17,30 @@ use Piwik\Log; abstract class Backend { /** - * The log message format string that turns a tag name, date-time and message into - * one string to log. - * - * @var string + * @var Formatter */ - private $logMessageFormat; + private $formatter; - public function __construct($logMessageFormat) + public function __construct(Formatter $formatter) { - $this->logMessageFormat = $logMessageFormat; + $this->formatter = $formatter; } public abstract function __invoke($level, $tag, $datetime, $message, Log $logger); /** - * Creates log message combining logging info including a log level, tag name, - * date time, and caller-provided log message. The log message can be set through - * the `[log] string_message_format` INI config option. By default it will - * create log messages like: - * - * **LEVEL [tag:datetime] log message** + * Formats the log message using the configured formatter. * * @param int $level * @param string $tag * @param string $datetime * @param string $message + * @param Log $logger * @return string */ - protected function formatMessage($level, $tag, $datetime, $message) + protected function formatMessage($level, $tag, $datetime, $message, Log $logger) { - return str_replace( - array("%tag%", "%message%", "%datetime%", "%level%"), - array($tag, trim($message), $datetime, $this->getStringLevel($level)), - $this->logMessageFormat - ); + return trim($this->formatter->format($message, $level, $tag, $datetime, $logger)); } protected function getStringLevel($level) diff --git a/core/Log/Backend/DatabaseBackend.php b/core/Log/Backend/DatabaseBackend.php index 2ffb7386d33583ad9eb3bacfd230023ee77c069e..5c7e8710bba01ad221f86fc1a3675e99f7b27b56 100644 --- a/core/Log/Backend/DatabaseBackend.php +++ b/core/Log/Backend/DatabaseBackend.php @@ -11,7 +11,6 @@ namespace Piwik\Log\Backend; use Piwik\Common; use Piwik\Db; use Piwik\Log; -use Piwik\Piwik; /** * Writes log to database. @@ -20,7 +19,8 @@ class DatabaseBackend extends Backend { public function __invoke($level, $tag, $datetime, $message, Log $logger) { - $message = $this->getMessageFormattedDatabase($level, $tag, $datetime, $message, $logger); + $message = $this->formatMessage($level, $tag, $datetime, $message, $logger); + if (empty($message)) { return; } @@ -31,37 +31,4 @@ class DatabaseBackend extends Backend Db::query($sql, array($tag, $datetime, self::getStringLevel($level), (string)$message)); } - - private function getMessageFormattedDatabase($level, $tag, $datetime, $message, $logger) - { - if (is_string($message)) { - $message = $this->formatMessage($level, $tag, $datetime, $message); - } else { - /** - * Triggered when trying to log an object to a database table. Plugins can use - * this event to convert objects to strings before they are logged. - * - * **Example** - * - * public function formatDatabaseMessage(&$message, $level, $tag, $datetime, $logger) { - * if ($message instanceof MyCustomDebugInfo) { - * $message = $message->formatForDatabase(); - * } - * } - * - * @param mixed &$message The object that is being logged. Event handlers should - * check if the object is of a certain type and if it is, - * set `$message` to the string that should be logged. - * @param int $level The log level used with this log entry. - * @param string $tag The current plugin that started logging (or if no plugin, - * the current class). - * @param string $datetime Datetime of the logging call. - * @param Log $logger The Log singleton. - */ - Piwik::postEvent(Log::FORMAT_DATABASE_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger)); - } - $message = trim($message); - - return $message; - } } diff --git a/core/Log/Backend/FileBackend.php b/core/Log/Backend/FileBackend.php index 4df90b91f289b4aff595d3ce7e08813e6e569b5b..6759827a77c3d83eb48c6db26afdbee9f6511348 100644 --- a/core/Log/Backend/FileBackend.php +++ b/core/Log/Backend/FileBackend.php @@ -10,7 +10,7 @@ namespace Piwik\Log\Backend; use Piwik\Filechecks; use Piwik\Log; -use Piwik\Piwik; +use Piwik\Log\Formatter\Formatter; /** * Writes log to file. @@ -24,19 +24,17 @@ class FileBackend extends Backend */ private $logToFilePath; - public function __construct($logMessageFormat, $logToFilePath) + public function __construct(Formatter $formatter, $logToFilePath) { $this->logToFilePath = $logToFilePath; - parent::__construct($logMessageFormat); + parent::__construct($formatter); } public function __invoke($level, $tag, $datetime, $message, Log $logger) { - $message = $this->getMessageFormattedFile($level, $tag, $datetime, $message, $logger); - if (empty($message)) { - return; - } + $message = $this->formatMessage($level, $tag, $datetime, $message, $logger); + $message = str_replace("\n", "\n ", $message) . "\n"; if (!@file_put_contents($this->logToFilePath, $message, FILE_APPEND) && !defined('PIWIK_TEST_MODE') @@ -45,39 +43,4 @@ class FileBackend extends Backend throw new \Exception($message); } } - - private function getMessageFormattedFile($level, $tag, $datetime, $message, Log $logger) - { - if (is_string($message)) { - $message = $this->formatMessage($level, $tag, $datetime, $message); - } else { - /** - * Triggered when trying to log an object to a file. Plugins can use - * this event to convert objects to strings before they are logged. - * - * **Example** - * - * public function formatFileMessage(&$message, $level, $tag, $datetime, $logger) { - * if ($message instanceof MyCustomDebugInfo) { - * $message = $message->formatForFile(); - * } - * } - * - * @param mixed &$message The object that is being logged. Event handlers should - * check if the object is of a certain type and if it is, - * set `$message` to the string that should be logged. - * @param int $level The log level used with this log entry. - * @param string $tag The current plugin that started logging (or if no plugin, - * the current class). - * @param string $datetime Datetime of the logging call. - * @param Log $logger The Log instance. - */ - Piwik::postEvent(Log::FORMAT_FILE_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger)); - } - - $message = trim($message); - $message = str_replace("\n", "\n ", $message); - - return $message . "\n"; - } } diff --git a/core/Log/Backend/ScreenBackend.php b/core/Log/Backend/ScreenBackend.php deleted file mode 100644 index 01e8a38fb7665d86f83ab7a297cdcb6a8075d258..0000000000000000000000000000000000000000 --- a/core/Log/Backend/ScreenBackend.php +++ /dev/null @@ -1,79 +0,0 @@ -<?php -/** - * Piwik - free/libre analytics platform - * - * @link http://piwik.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - -namespace Piwik\Log\Backend; - -use Piwik\Common; -use Piwik\Log; -use Piwik\Piwik; - -/** - * Writes log to screen. - */ -class ScreenBackend extends Backend -{ - public function __invoke($level, $tag, $datetime, $message, Log $logger) - { - $message = $this->getMessageFormattedScreen($level, $tag, $datetime, $message, $logger); - if (empty($message)) { - return; - } - - echo $message; - } - - public function getMessageFormattedScreen($level, $tag, $datetime, $message, Log $logger) - { - static $currentRequestKey; - - if (empty($currentRequestKey)) { - $currentRequestKey = substr(Common::generateUniqId(), 0, 5); - } - - if (is_string($message)) { - if (!defined('PIWIK_TEST_MODE')) { - $message = '[' . $currentRequestKey . '] ' . $message; - } - $message = $this->formatMessage($level, $tag, $datetime, $message); - - if (!Common::isPhpCliMode()) { - $message = Common::sanitizeInputValue($message); - $message = '<pre>' . $message . '</pre>'; - } - } else { - /** - * Triggered when trying to log an object to the screen. Plugins can use - * this event to convert objects to strings before they are logged. - * - * The result of this callback can be HTML so no sanitization is done on the result. - * This means **YOU MUST SANITIZE THE MESSAGE YOURSELF** if you use this event. - * - * **Example** - * - * public function formatScreenMessage(&$message, $level, $tag, $datetime, $logger) { - * if ($message instanceof MyCustomDebugInfo) { - * $message = Common::sanitizeInputValue($message->formatForScreen()); - * } - * } - * - * @param mixed &$message The object that is being logged. Event handlers should - * check if the object is of a certain type and if it is, - * set `$message` to the string that should be logged. - * @param int $level The log level used with this log entry. - * @param string $tag The current plugin that started logging (or if no plugin, - * the current class). - * @param string $datetime Datetime of the logging call. - * @param Log $logger The Log singleton. - */ - Piwik::postEvent(Log::FORMAT_SCREEN_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger)); - } - $message = trim($message); - - return $message . "\n"; - } -} diff --git a/core/Log/Backend/StdErrBackend.php b/core/Log/Backend/StdErrBackend.php new file mode 100644 index 0000000000000000000000000000000000000000..c2ef47cc7511496a46b9823cb83d075903a9cdd0 --- /dev/null +++ b/core/Log/Backend/StdErrBackend.php @@ -0,0 +1,56 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Log\Backend; + +use Piwik\Log; +use Piwik\Log\Formatter\Formatter; + +/** + * Writes log to stderr. + */ +class StdErrBackend extends Backend +{ + /** + * @var bool + */ + private $isLoggingToStdOut; + + public function __construct(Formatter $formatter, $isLoggingToStdOut) + { + $this->isLoggingToStdOut = $isLoggingToStdOut; + + parent::__construct($formatter); + } + + public function __invoke($level, $tag, $datetime, $message, Log $logger) + { + if ($level != Log::ERROR) { + return; + } + + $message = $this->formatMessage($level, $tag, $datetime, $message, $logger) . "\n"; + + // Do not log on stderr during tests (prevent display of errors in CI output) + if (! defined('PIWIK_TEST_MODE')) { + $this->writeToStdErr($message); + } + + // This is the result of an old hack, I guess to force the error message to be displayed in case of errors + // TODO we should get rid of it somehow + if (! $this->isLoggingToStdOut) { + echo $message; + } + } + + private function writeToStdErr($message) + { + $fe = fopen('php://stderr', 'w'); + fwrite($fe, $message); + } +} diff --git a/core/Log/Backend/StdOutBackend.php b/core/Log/Backend/StdOutBackend.php new file mode 100644 index 0000000000000000000000000000000000000000..2429289404d306714862f0799179c34ef4f717ea --- /dev/null +++ b/core/Log/Backend/StdOutBackend.php @@ -0,0 +1,24 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Log\Backend; + +use Piwik\Log; + +/** + * Writes log to stdout. + */ +class StdOutBackend extends Backend +{ + public function __invoke($level, $tag, $datetime, $message, Log $logger) + { + $message = $this->formatMessage($level, $tag, $datetime, $message, $logger); + + echo $message . "\n"; + } +} diff --git a/core/Log/Formatter/AddRequestIdFormatter.php b/core/Log/Formatter/AddRequestIdFormatter.php new file mode 100644 index 0000000000000000000000000000000000000000..ee061d1d0d9334ed771e5bf20b809c066e448527 --- /dev/null +++ b/core/Log/Formatter/AddRequestIdFormatter.php @@ -0,0 +1,40 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Log\Formatter; + +use Piwik\Common; +use Piwik\Log; + +/** + * Adds a unique "request id" to the log message to follow log entries for each HTTP request. + */ +class AddRequestIdFormatter extends Formatter +{ + public function format($message, $level, $tag, $datetime, Log $logger) + { + static $currentRequestKey; + + if (empty($currentRequestKey)) { + $currentRequestKey = substr(Common::generateUniqId(), 0, 5); + } + + $message = $this->next($message, $level, $tag, $datetime, $logger); + + if (! is_string($message)) { + return $message; + } + + // Decorate the error message with the "request id" + if (!defined('PIWIK_TEST_MODE')) { + $message = '[' . $currentRequestKey . '] ' . $message; + } + + return $message; + } +} diff --git a/core/Log/Formatter/ErrorHtmlFormatter.php b/core/Log/Formatter/ErrorHtmlFormatter.php index 339798d7e4dff3a8f935e283b4697944b420aa01..3af11282cd219590f3204c2ea4f98e01e8e0bd6a 100644 --- a/core/Log/Formatter/ErrorHtmlFormatter.php +++ b/core/Log/Formatter/ErrorHtmlFormatter.php @@ -16,12 +16,12 @@ use Piwik\Version; /** * Formats a log message containing a Piwik\Error object into an HTML string. */ -class ErrorHtmlFormatter implements Formatter +class ErrorHtmlFormatter extends Formatter { public function format($message, $level, $tag, $datetime, Log $logger) { if (! $message instanceof Error) { - return $message; + return $this->next($message, $level, $tag, $datetime, $logger); } $errno = $message->errno & error_reporting(); diff --git a/core/Log/Formatter/ErrorTextFormatter.php b/core/Log/Formatter/ErrorTextFormatter.php index 9ba2393e105562bcc94f51232b30691600f4a505..ba9b18d51ac031695d60b0634305526dd1de0d56 100644 --- a/core/Log/Formatter/ErrorTextFormatter.php +++ b/core/Log/Formatter/ErrorTextFormatter.php @@ -14,12 +14,12 @@ use Piwik\Log; /** * Formats a log message containing a Piwik\Error object into a textual string. */ -class ErrorTextFormatter implements Formatter +class ErrorTextFormatter extends Formatter { public function format($message, $level, $tag, $datetime, Log $logger) { if (! $message instanceof Error) { - return $message; + return $this->next($message, $level, $tag, $datetime, $logger); } $message = $message->errfile . '(' . $message->errline . '): ' . Error::getErrNoString($message->errno) diff --git a/core/Log/Formatter/ExceptionHtmlFormatter.php b/core/Log/Formatter/ExceptionHtmlFormatter.php index 1a7a37db4c87a100b12e8ca712928539c203e5b7..309dbafe7f233972ddb2e50d43b8dfcb51c87965 100644 --- a/core/Log/Formatter/ExceptionHtmlFormatter.php +++ b/core/Log/Formatter/ExceptionHtmlFormatter.php @@ -15,12 +15,12 @@ use Piwik\Log; /** * Formats a log message containing an exception object into an HTML response. */ -class ExceptionHtmlFormatter implements Formatter +class ExceptionHtmlFormatter extends Formatter { public function format($message, $level, $tag, $datetime, Log $logger) { if (! $message instanceof \Exception) { - return $message; + return $this->next($message, $level, $tag, $datetime, $logger); } Common::sendHeader('Content-Type: text/html; charset=utf-8'); diff --git a/core/Log/Formatter/ExceptionTextFormatter.php b/core/Log/Formatter/ExceptionTextFormatter.php index 43edf1820d50e923234ce2d17a9ed17585a85033..24ab88c270b62ad00b1a514d0fba134a36c3b596 100644 --- a/core/Log/Formatter/ExceptionTextFormatter.php +++ b/core/Log/Formatter/ExceptionTextFormatter.php @@ -14,12 +14,12 @@ use Piwik\Log; /** * Formats a log message containing an exception object into a textual string. */ -class ExceptionTextFormatter implements Formatter +class ExceptionTextFormatter extends Formatter { public function format($message, $level, $tag, $datetime, Log $logger) { if (! $message instanceof \Exception) { - return $message; + return $this->next($message, $level, $tag, $datetime, $logger); } $message = sprintf("%s(%d): %s\n%s", $message->getFile(), $message->getLine(), $message->getMessage(), diff --git a/core/Log/Formatter/Formatter.php b/core/Log/Formatter/Formatter.php index d533ba13aaf046b0df9526f8b8a3fda6aaf3fbb0..47d501e247a7138d67dd508eddafb4be1572c446 100644 --- a/core/Log/Formatter/Formatter.php +++ b/core/Log/Formatter/Formatter.php @@ -12,9 +12,17 @@ use Piwik\Log; /** * Formats a log message. + * + * Follows the Chain of responsibility design pattern, so don't forget to call `$this->next(...)` + * at the end of the `format()` method. */ -interface Formatter +abstract class Formatter { + /** + * @var Formatter|null + */ + protected $next; + /** * @param mixed $message Log message. * @param int $level The log level used with this log entry. @@ -24,5 +32,24 @@ interface Formatter * * @return mixed */ - public function format($message, $level, $tag, $datetime, Log $logger); + public abstract function format($message, $level, $tag, $datetime, Log $logger); + + /** + * Chain of responsibility pattern. + * + * @param Formatter $formatter + */ + public function setNext(Formatter $formatter) + { + $this->next = $formatter; + } + + protected function next($message, $level, $tag, $datetime, Log $logger) + { + if (! $this->next) { + return $message; + } + + return $this->next->format($message, $level, $tag, $datetime, $logger); + } } diff --git a/core/Log/Formatter/HtmlPreFormatter.php b/core/Log/Formatter/HtmlPreFormatter.php new file mode 100644 index 0000000000000000000000000000000000000000..e8c095c04719e4afa21d08981b34b304ef9f59a1 --- /dev/null +++ b/core/Log/Formatter/HtmlPreFormatter.php @@ -0,0 +1,34 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Log\Formatter; + +use Piwik\Common; +use Piwik\Log; + +/** + * Formats the message into `<pre></pre>` HTML tags. + */ +class HtmlPreFormatter extends Formatter +{ + public function format($message, $level, $tag, $datetime, Log $logger) + { + $message = $this->next($message, $level, $tag, $datetime, $logger); + + if (! is_string($message)) { + return $message; + } + + if (!Common::isPhpCliMode()) { + $message = Common::sanitizeInputValue($message); + $message = '<pre>' . $message . '</pre>'; + } + + return $message; + } +} diff --git a/core/Log/Formatter/LineMessageFormatter.php b/core/Log/Formatter/LineMessageFormatter.php new file mode 100644 index 0000000000000000000000000000000000000000..87bc54dfa145dd9f5c77aa841717b3a3ad66aa12 --- /dev/null +++ b/core/Log/Formatter/LineMessageFormatter.php @@ -0,0 +1,58 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Log\Formatter; + +use Piwik\Log; + +/** + * Formats a log message into a single line of text. + */ +class LineMessageFormatter extends Formatter +{ + /** + * The log message format string that turns a tag name, date-time and message into + * one string to log. + * + * @var string + */ + private $logMessageFormat; + + public function __construct($logMessageFormat) + { + $this->logMessageFormat = $logMessageFormat; + } + + public function format($message, $level, $tag, $datetime, Log $logger) + { + if (! is_string($message)) { + throw new \InvalidArgumentException('Trying to log a message that is not a string'); + } + + $message = str_replace( + array("%tag%", "%message%", "%datetime%", "%level%"), + array($tag, trim($message), $datetime, $this->getStringLevel($level)), + $this->logMessageFormat + ); + + return $this->next($message, $level, $tag, $datetime, $logger); + } + + private function getStringLevel($level) + { + static $levelToName = array( + Log::NONE => 'NONE', + Log::ERROR => 'ERROR', + Log::WARN => 'WARN', + Log::INFO => 'INFO', + Log::DEBUG => 'DEBUG', + Log::VERBOSE => 'VERBOSE' + ); + return $levelToName[$level]; + } +} diff --git a/core/Log/LoggerFactory.php b/core/Log/LoggerFactory.php index 3e48c63fdf21531ba7346f6a0b897a12ca592022..22ff4a0b9957f4890c255f674885359ac3189a6b 100644 --- a/core/Log/LoggerFactory.php +++ b/core/Log/LoggerFactory.php @@ -14,8 +14,15 @@ use Piwik\Config; use Piwik\Log; use Piwik\Log\Backend\DatabaseBackend; use Piwik\Log\Backend\FileBackend; -use Piwik\Log\Backend\ScreenBackend; -use Piwik\Log\Processor\SprintfProcessor; +use Piwik\Log\Backend\StdOutBackend; +use Piwik\Log\Backend\StdErrBackend; +use Piwik\Log\Formatter\AddRequestIdFormatter; +use Piwik\Log\Formatter\ErrorHtmlFormatter; +use Piwik\Log\Formatter\ErrorTextFormatter; +use Piwik\Log\Formatter\ExceptionHtmlFormatter; +use Piwik\Log\Formatter\ExceptionTextFormatter; +use Piwik\Log\Formatter\HtmlPreFormatter; +use Piwik\Log\Formatter\LineMessageFormatter; use Piwik\Piwik; class LoggerFactory @@ -78,6 +85,10 @@ class LoggerFactory $writers[$writerName] = $availableWriters[$writerName]; } + // Always add the stderr backend + $isLoggingToStdOut = isset($writers['screen']); + $writers['stderr'] = new StdErrBackend(self::getScreenFormatter($messageFormat), $isLoggingToStdOut); + return $writers; } @@ -141,7 +152,7 @@ class LoggerFactory } } - private static function getAvailableWriters($logMessageFormat, $logFilePath) + private static function getAvailableWriters($messageFormat, $logFilePath) { $writers = array(); @@ -173,10 +184,49 @@ class LoggerFactory */ Piwik::postEvent(Log::GET_AVAILABLE_WRITERS_EVENT, array(&$writers)); - $writers['file'] = new FileBackend($logMessageFormat, $logFilePath); - $writers['screen'] = new ScreenBackend($logMessageFormat); - $writers['database'] = new DatabaseBackend($logMessageFormat); + $writers['file'] = new FileBackend(self::getFileAndDbFormatter($messageFormat), $logFilePath); + $writers['screen'] = new StdOutBackend(self::getScreenFormatter($messageFormat)); + $writers['database'] = new DatabaseBackend(self::getFileAndDbFormatter($messageFormat)); return $writers; } + + public static function createScreenBackend($messageFormat) + { + return new StdOutBackend(self::getScreenFormatter($messageFormat)); + } + + private static function getFileAndDbFormatter($messageFormat) + { + // Chain of responsibility pattern + $lineFormatter = new LineMessageFormatter($messageFormat); + + $exceptionFormatter = new ExceptionTextFormatter(); + $exceptionFormatter->setNext($lineFormatter); + + $errorFormatter = new ErrorTextFormatter(); + $errorFormatter->setNext($exceptionFormatter); + + return $errorFormatter; + } + + private static function getScreenFormatter($messageFormat) + { + // Chain of responsibility pattern + $lineFormatter = new LineMessageFormatter($messageFormat); + + $addRequestIdFormatter = new AddRequestIdFormatter(); + $addRequestIdFormatter->setNext($lineFormatter); + + $htmlPreFormatter = new HtmlPreFormatter(); + $htmlPreFormatter->setNext($addRequestIdFormatter); + + $exceptionFormatter = new ExceptionHtmlFormatter(); + $exceptionFormatter->setNext($htmlPreFormatter); + + $errorFormatter = new ErrorHtmlFormatter(); + $errorFormatter->setNext($exceptionFormatter); + + return $errorFormatter; + } }