From 689e5d16532acdb6bdeebf407e5d256046d80555 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 2 Jul 2013 21:47:46 -0400 Subject: [PATCH] renamed "logging" to "log". --- framework/yii/YiiBase.php | 36 +--- framework/yii/base/Application.php | 12 ++ framework/yii/classes.php | 13 +- framework/yii/debug/LogTarget.php | 2 +- framework/yii/log/DbTarget.php | 93 ++++++++++ framework/yii/log/EmailTarget.php | 72 ++++++++ framework/yii/log/FileTarget.php | 115 ++++++++++++ framework/yii/log/Logger.php | 317 ++++++++++++++++++++++++++++++++++ framework/yii/log/Router.php | 98 +++++++++++ framework/yii/log/Target.php | 245 ++++++++++++++++++++++++++ framework/yii/logging/DbTarget.php | 93 ---------- framework/yii/logging/EmailTarget.php | 72 -------- framework/yii/logging/FileTarget.php | 115 ------------ framework/yii/logging/Logger.php | 272 ----------------------------- framework/yii/logging/Router.php | 98 ----------- framework/yii/logging/Target.php | 245 -------------------------- 16 files changed, 966 insertions(+), 932 deletions(-) create mode 100644 framework/yii/log/DbTarget.php create mode 100644 framework/yii/log/EmailTarget.php create mode 100644 framework/yii/log/FileTarget.php create mode 100644 framework/yii/log/Logger.php create mode 100644 framework/yii/log/Router.php create mode 100644 framework/yii/log/Target.php delete mode 100644 framework/yii/logging/DbTarget.php delete mode 100644 framework/yii/logging/EmailTarget.php delete mode 100644 framework/yii/logging/FileTarget.php delete mode 100644 framework/yii/logging/Logger.php delete mode 100644 framework/yii/logging/Router.php delete mode 100644 framework/yii/logging/Target.php diff --git a/framework/yii/YiiBase.php b/framework/yii/YiiBase.php index 5c48234..d91df88 100644 --- a/framework/yii/YiiBase.php +++ b/framework/yii/YiiBase.php @@ -10,7 +10,7 @@ use yii\base\Exception; use yii\base\InvalidConfigException; use yii\base\InvalidParamException; use yii\base\UnknownClassException; -use yii\logging\Logger; +use yii\log\Logger; /** * Gets the application start timestamp. @@ -478,7 +478,7 @@ class YiiBase public static function trace($message, $category = 'application') { if (YII_DEBUG) { - self::getLogger()->log($message, Logger::LEVEL_TRACE, $category); + self::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category); } } @@ -491,7 +491,7 @@ class YiiBase */ public static function error($message, $category = 'application') { - self::getLogger()->log($message, Logger::LEVEL_ERROR, $category); + self::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category); } /** @@ -503,7 +503,7 @@ class YiiBase */ public static function warning($message, $category = 'application') { - self::getLogger()->log($message, Logger::LEVEL_WARNING, $category); + self::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category); } /** @@ -515,7 +515,7 @@ class YiiBase */ public static function info($message, $category = 'application') { - self::getLogger()->log($message, Logger::LEVEL_INFO, $category); + self::$app->getLog()->log($message, Logger::LEVEL_INFO, $category); } /** @@ -537,7 +537,7 @@ class YiiBase */ public static function beginProfile($token, $category = 'application') { - self::getLogger()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); + self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); } /** @@ -549,29 +549,7 @@ class YiiBase */ public static function endProfile($token, $category = 'application') { - self::getLogger()->log($token, Logger::LEVEL_PROFILE_END, $category); - } - - /** - * Returns the message logger object. - * @return \yii\logging\Logger message logger - */ - public static function getLogger() - { - if (self::$_logger !== null) { - return self::$_logger; - } else { - return self::$_logger = new Logger; - } - } - - /** - * Sets the logger object. - * @param Logger $logger the logger object. - */ - public static function setLogger($logger) - { - self::$_logger = $logger; + self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category); } /** diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index 2d2157c..9b1dec0 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -266,6 +266,15 @@ abstract class Application extends Module } /** + * Returns the log component. + * @return \yii\log\Logger the log component + */ + public function getLog() + { + return $this->getComponent('log'); + } + + /** * Returns the error handler component. * @return ErrorHandler the error handler application component. */ @@ -344,6 +353,9 @@ abstract class Application extends Module public function registerCoreComponents() { $this->setComponents(array( + 'log' => array( + 'class' => 'yii\log\Logger', + ), 'errorHandler' => array( 'class' => 'yii\base\ErrorHandler', ), diff --git a/framework/yii/classes.php b/framework/yii/classes.php index 81d02e4..ce40383 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -41,13 +41,12 @@ return array( 'yii\web\AssetBundle' => YII_PATH . '/web/AssetBundle.php', 'yii\web\AssetConverter' => YII_PATH . '/web/AssetConverter.php', 'yii\web\HeaderCollection' => YII_PATH . '/web/HeaderCollection.php', -'yii\logging\Target' => YII_PATH . '/logging/Target.php', -'yii\logging\DebugTarget' => YII_PATH . '/logging/DebugTarget.php', -'yii\logging\Router' => YII_PATH . '/logging/Router.php', -'yii\logging\Logger' => YII_PATH . '/logging/Logger.php', -'yii\logging\EmailTarget' => YII_PATH . '/logging/EmailTarget.php', -'yii\logging\DbTarget' => YII_PATH . '/logging/DbTarget.php', -'yii\logging\FileTarget' => YII_PATH . '/logging/FileTarget.php', +'yii\log\Target' => YII_PATH . '/logging/Target.php', +'yii\log\DebugTarget' => YII_PATH . '/logging/DebugTarget.php', +'yii\log\Logger' => YII_PATH . '/logging/Logger.php', +'yii\log\EmailTarget' => YII_PATH . '/logging/EmailTarget.php', +'yii\log\DbTarget' => YII_PATH . '/logging/DbTarget.php', +'yii\log\FileTarget' => YII_PATH . '/logging/FileTarget.php', 'yii\widgets\ActiveField' => YII_PATH . '/widgets/ActiveField.php', 'yii\widgets\Captcha' => YII_PATH . '/widgets/Captcha.php', 'yii\widgets\ListPager' => YII_PATH . '/widgets/ListPager.php', diff --git a/framework/yii/debug/LogTarget.php b/framework/yii/debug/LogTarget.php index 584da6c..82aa127 100644 --- a/framework/yii/debug/LogTarget.php +++ b/framework/yii/debug/LogTarget.php @@ -8,7 +8,7 @@ namespace yii\debug; use Yii; -use yii\logging\Target; +use yii\log\Target; /** * @author Qiang Xue diff --git a/framework/yii/log/DbTarget.php b/framework/yii/log/DbTarget.php new file mode 100644 index 0000000..16b1eab --- /dev/null +++ b/framework/yii/log/DbTarget.php @@ -0,0 +1,93 @@ + + * @since 2.0 + */ +class DbTarget extends Target +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the DbTarget object is created, if you want to change this property, you should only assign it + * with a DB connection object. + */ + public $db = 'db'; + /** + * @var string name of the DB table to store cache content. + * The table should be pre-created as follows: + * + * ~~~ + * CREATE TABLE tbl_log ( + * id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + * level INTEGER, + * category VARCHAR(255), + * log_time INTEGER, + * message TEXT, + * INDEX idx_log_level (level), + * INDEX idx_log_category (category) + * ) + * ~~~ + * + * Note that the 'id' column must be created as an auto-incremental column. + * The above SQL uses the MySQL syntax. If you are using other DBMS, you need + * to adjust it accordingly. For example, in PostgreSQL, it should be `id SERIAL PRIMARY KEY`. + * + * The indexes declared above are not required. They are mainly used to improve the performance + * of some queries about message levels and categories. Depending on your actual needs, you may + * want to create additional indexes (e.g. index on `log_time`). + */ + public $logTable = 'tbl_log'; + + /** + * Initializes the DbTarget component. + * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. + * @throws InvalidConfigException if [[db]] is invalid. + */ + public function init() + { + parent::init(); + if (is_string($this->db)) { + $this->db = Yii::$app->getComponent($this->db); + } + if (!$this->db instanceof Connection) { + throw new InvalidConfigException("DbTarget::db must be either a DB connection instance or the application component ID of a DB connection."); + } + } + + /** + * Stores log messages to DB. + * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure + * of each message. + */ + public function export($messages) + { + $tableName = $this->db->quoteTableName($this->logTable); + $sql = "INSERT INTO $tableName ([[level]], [[category]], [[log_time]], [[message]]) + VALUES (:level, :category, :log_time, :message)"; + $command = $this->db->createCommand($sql); + foreach ($messages as $message) { + $command->bindValues(array( + ':level' => $message[1], + ':category' => $message[2], + ':log_time' => $message[3], + ':message' => $message[0], + ))->execute(); + } + } +} diff --git a/framework/yii/log/EmailTarget.php b/framework/yii/log/EmailTarget.php new file mode 100644 index 0000000..df4f9e0 --- /dev/null +++ b/framework/yii/log/EmailTarget.php @@ -0,0 +1,72 @@ + + * @since 2.0 + */ +class EmailTarget extends Target +{ + /** + * @var array list of destination email addresses. + */ + public $emails = array(); + /** + * @var string email subject + */ + public $subject; + /** + * @var string email sent-from address + */ + public $sentFrom; + /** + * @var array list of additional headers to use when sending an email. + */ + public $headers = array(); + + /** + * Sends log messages to specified email addresses. + * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure + * of each message. + */ + public function export($messages) + { + $body = ''; + foreach ($messages as $message) { + $body .= $this->formatMessage($message); + } + $body = wordwrap($body, 70); + $subject = $this->subject === null ? \Yii::t('yii', 'Application Log') : $this->subject; + foreach ($this->emails as $email) { + $this->sendEmail($subject, $body, $email, $this->sentFrom, $this->headers); + } + } + + /** + * Sends an email. + * @param string $subject email subject + * @param string $body email body + * @param string $sentTo sent-to email address + * @param string $sentFrom sent-from email address + * @param array $headers additional headers to be used when sending the email + */ + protected function sendEmail($subject, $body, $sentTo, $sentFrom, $headers) + { + if ($sentFrom !== null) { + $headers[] = "From: {$sentFrom}"; + } + mail($sentTo, $subject, $body, implode("\r\n", $headers)); + } +} diff --git a/framework/yii/log/FileTarget.php b/framework/yii/log/FileTarget.php new file mode 100644 index 0000000..c4cd40d --- /dev/null +++ b/framework/yii/log/FileTarget.php @@ -0,0 +1,115 @@ + + * @since 2.0 + */ +class FileTarget extends Target +{ + /** + * @var string log file path or path alias. If not set, it will use the "runtime/logs/app.log" file. + * The directory containing the log files will be automatically created if not existing. + */ + public $logFile; + /** + * @var integer maximum log file size, in kilo-bytes. Defaults to 10240, meaning 10MB. + */ + public $maxFileSize = 10240; // in KB + /** + * @var integer number of log files used for rotation. Defaults to 5. + */ + public $maxLogFiles = 5; + + + /** + * Initializes the route. + * This method is invoked after the route is created by the route manager. + */ + public function init() + { + parent::init(); + if ($this->logFile === null) { + $this->logFile = Yii::$app->getRuntimePath() . '/logs/app.log'; + } else { + $this->logFile = Yii::getAlias($this->logFile); + } + $logPath = dirname($this->logFile); + if (!is_dir($logPath)) { + @mkdir($logPath, 0777, true); + } + if ($this->maxLogFiles < 1) { + $this->maxLogFiles = 1; + } + if ($this->maxFileSize < 1) { + $this->maxFileSize = 1; + } + } + + /** + * Sends log messages to specified email addresses. + * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure + * of each message. + * @throws InvalidConfigException if unable to open the log file for writing + */ + public function export($messages) + { + $text = ''; + foreach ($messages as $message) { + $text .= $this->formatMessage($message); + } + if (($fp = @fopen($this->logFile, 'a')) === false) { + throw new InvalidConfigException("Unable to append to log file: {$this->logFile}"); + } + @flock($fp, LOCK_EX); + if (@filesize($this->logFile) > $this->maxFileSize * 1024) { + $this->rotateFiles(); + @flock($fp, LOCK_UN); + @fclose($fp); + @file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX); + } else { + @fwrite($fp, $text); + @flock($fp, LOCK_UN); + @fclose($fp); + } + } + + /** + * Rotates log files. + */ + protected function rotateFiles() + { + $file = $this->logFile; + for ($i = $this->maxLogFiles; $i > 0; --$i) { + $rotateFile = $file . '.' . $i; + if (is_file($rotateFile)) { + // suppress errors because it's possible multiple processes enter into this section + if ($i === $this->maxLogFiles) { + @unlink($rotateFile); + } else { + @rename($rotateFile, $file . '.' . ($i + 1)); + } + } + } + if (is_file($file)) { + @rename($file, $file . '.1'); // suppress errors because it's possible multiple processes enter into this section + } + } +} diff --git a/framework/yii/log/Logger.php b/framework/yii/log/Logger.php new file mode 100644 index 0000000..c0bc16b --- /dev/null +++ b/framework/yii/log/Logger.php @@ -0,0 +1,317 @@ +log`. + * You can call the method [[log()]] to record a single log message. For convenience, a set of shortcut + * methods are provided for logging messages of various severity levels via the [[Yii]] class: + * + * - [[Yii::trace()]] + * - [[Yii::error()]] + * - [[Yii::warning()]] + * - [[Yii::info()]] + * - [[Yii::beginProfile()]] + * - [[Yii::endProfile()]] + * + * When enough messages are accumulated in the logger, or when the current request finishes, + * the logged messages will be sent to different [[targets]], such as log files, emails. + * + * You may configure the targets in application configuration, like the following: + * + * ~~~ + * array( + * 'components' => array( + * 'log' => array( + * 'targets' => array( + * 'file' => array( + * 'class' => 'yii\log\FileTarget', + * 'levels' => array('trace', 'info'), + * 'categories' => array('yii\*'), + * ), + * 'email' => array( + * 'class' => 'yii\log\EmailTarget', + * 'levels' => array('error', 'warning'), + * 'emails' => array('admin@example.com'), + * ), + * ), + * ), + * ), + * ) + * ~~~ + * + * Each log target can have a name and can be referenced via the [[targets]] property + * as follows: + * + * ~~~ + * Yii::$app->log->targets['file']->enabled = false; + * ~~~ + * + * When the application ends or [[flushInterval]] is reached, Logger will call [[flush()]] + * to send logged messages to different log targets, such as file, email, Web. + * + * @author Qiang Xue + * @since 2.0 + */ +class Logger extends Component +{ + /** + * Error message level. An error message is one that indicates the abnormal termination of the + * application and may require developer's handling. + */ + const LEVEL_ERROR = 0x01; + /** + * Warning message level. A warning message is one that indicates some abnormal happens but + * the application is able to continue to run. Developers should pay attention to this message. + */ + const LEVEL_WARNING = 0x02; + /** + * Informational message level. An informational message is one that includes certain information + * for developers to review. + */ + const LEVEL_INFO = 0x04; + /** + * Tracing message level. An tracing message is one that reveals the code execution flow. + */ + const LEVEL_TRACE = 0x08; + /** + * Profiling message level. This indicates the message is for profiling purpose. + */ + const LEVEL_PROFILE = 0x40; + /** + * Profiling message level. This indicates the message is for profiling purpose. It marks the + * beginning of a profiling block. + */ + const LEVEL_PROFILE_BEGIN = 0x50; + /** + * Profiling message level. This indicates the message is for profiling purpose. It marks the + * end of a profiling block. + */ + const LEVEL_PROFILE_END = 0x60; + + + /** + * @var integer how many messages should be logged before they are flushed from memory and sent to targets. + * Defaults to 1000, meaning the [[flush]] method will be invoked once every 1000 messages logged. + * Set this property to be 0 if you don't want to flush messages until the application terminates. + * This property mainly affects how much memory will be taken by the logged messages. + * A smaller value means less memory, but will increase the execution time due to the overhead of [[flush()]]. + */ + public $flushInterval = 1000; + /** + * @var array logged messages. This property is managed by [[log()]] and [[flush()]]. + * Each log message is of the following structure: + * + * ~~~ + * array( + * [0] => message (mixed, can be a string or some complex data, such as an exception object) + * [1] => level (integer) + * [2] => category (string) + * [3] => timestamp (float, obtained by microtime(true)) + * ) + * ~~~ + */ + public $messages = array(); + /** + * @var array the log targets. Each array element represents a single [[Target|log target]] instance + * or the configuration for creating the log target instance. + */ + public $targets = array(); + + + /** + * @var string + */ + private $_tag; + + + /** + * Initializes the logger by registering [[flush()]] as a shutdown function. + */ + public function init() + { + parent::init(); + register_shutdown_function(array($this, 'flush'), true); + } + + /** + * Logs a message with the given type and category. + * If `YII_DEBUG` is true and `YII_TRACE_LEVEL` is greater than 0, then additional + * call stack information about application code will be appended to the message. + * @param string $message the message to be logged. + * @param integer $level the level of the message. This must be one of the following: + * `Logger::LEVEL_ERROR`, `Logger::LEVEL_WARNING`, `Logger::LEVEL_INFO`, `Logger::LEVEL_TRACE`, + * `Logger::LEVEL_PROFILE_BEGIN`, `Logger::LEVEL_PROFILE_END`. + * @param string $category the category of the message. + */ + public function log($message, $level, $category = 'application') + { + $time = microtime(true); + if (YII_DEBUG && YII_TRACE_LEVEL > 0) { + $traces = debug_backtrace(); + $count = 0; + foreach ($traces as $trace) { + if (isset($trace['file'], $trace['line']) && strpos($trace['file'], YII_PATH) !== 0) { + $message .= "\nin {$trace['file']} ({$trace['line']})"; + if (++$count >= YII_TRACE_LEVEL) { + break; + } + } + } + } + $this->messages[] = array($message, $level, $category, $time); + if ($this->flushInterval > 0 && count($this->messages) >= $this->flushInterval) { + $this->flush(); + } + } + + /** + * Flushes log messages from memory to targets. + * This method will trigger an [[EVENT_FLUSH]] or [[EVENT_FINAL_FLUSH]] event depending on the $final value. + * @param boolean $final whether this is a final call during a request. + */ + public function flush($final = false) + { + if ($this->router) { + $this->router->dispatch($this->messages, $final); + } + $this->messages = array(); + } + + /** + * @return string a tag that uniquely identifies the current request. + */ + public function getTag() + { + if ($this->_tag === null) { + $this->_tag = date('Ymd-His', microtime(true)); + } + return $this->_tag; + } + + /** + * @param string $tag a tag that uniquely identifies the current request. + */ + public function setTag($tag) + { + $this->_tag = $tag; + } + + /** + * Returns the total elapsed time since the start of the current request. + * This method calculates the difference between now and the timestamp + * defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning + * of [[YiiBase]] class file. + * @return float the total elapsed time in seconds for current request. + */ + public function getElapsedTime() + { + return microtime(true) - YII_BEGIN_TIME; + } + + /** + * Returns the profiling results. + * + * By default, all profiling results will be returned. You may provide + * `$categories` and `$excludeCategories` as parameters to retrieve the + * results that you are interested in. + * + * @param array $categories list of categories that you are interested in. + * You can use an asterisk at the end of a category to do a prefix match. + * For example, 'yii\db\*' will match categories starting with 'yii\db\', + * such as 'yii\db\Connection'. + * @param array $excludeCategories list of categories that you want to exclude + * @return array the profiling results. Each array element has the following structure: + * `array($token, $category, $time)`. + */ + public function getProfiling($categories = array(), $excludeCategories = array()) + { + $timings = $this->calculateTimings(); + if (empty($categories) && empty($excludeCategories)) { + return $timings; + } + + foreach ($timings as $i => $timing) { + $matched = empty($categories); + foreach ($categories as $category) { + $prefix = rtrim($category, '*'); + if (strpos($timing[1], $prefix) === 0 && ($timing[1] === $category || $prefix !== $category)) { + $matched = true; + break; + } + } + + if ($matched) { + foreach ($excludeCategories as $category) { + $prefix = rtrim($category, '*'); + foreach ($timings as $i => $timing) { + if (strpos($timing[1], $prefix) === 0 && ($timing[1] === $category || $prefix !== $category)) { + $matched = false; + break; + } + } + } + } + + if (!$matched) { + unset($timings[$i]); + } + } + return array_values($timings); + } + + /** + * Returns the statistical results of DB queries. + * The results returned include the number of SQL statements executed and + * the total time spent. + * @return array the first element indicates the number of SQL statements executed, + * and the second element the total time spent in SQL execution. + */ + public function getDbProfiling() + { + $timings = $this->getProfiling(array('yii\db\Command::query', 'yii\db\Command::execute')); + $count = count($timings); + $time = 0; + foreach ($timings as $timing) { + $time += $timing[1]; + } + return array($count, $time); + } + + private function calculateTimings() + { + $timings = array(); + + $stack = array(); + foreach ($this->messages as $log) { + list($token, $level, $category, $timestamp) = $log; + if ($level == self::LEVEL_PROFILE_BEGIN) { + $stack[] = $log; + } elseif ($level == self::LEVEL_PROFILE_END) { + if (($last = array_pop($stack)) !== null && $last[0] === $token) { + $timings[] = array($token, $category, $timestamp - $last[3]); + } else { + throw new InvalidConfigException("Unmatched profiling block: $token"); + } + } + } + + $now = microtime(true); + while (($last = array_pop($stack)) !== null) { + $delta = $now - $last[3]; + $timings[] = array($last[0], $last[2], $delta); + } + + return $timings; + } +} diff --git a/framework/yii/log/Router.php b/framework/yii/log/Router.php new file mode 100644 index 0000000..c7d9ac3 --- /dev/null +++ b/framework/yii/log/Router.php @@ -0,0 +1,98 @@ + array('log'), + * 'components' => array( + * 'log' => array( + * 'class' => 'yii\log\Router', + * 'targets' => array( + * 'file' => array( + * 'class' => 'yii\log\FileTarget', + * 'levels' => array('trace', 'info'), + * 'categories' => array('yii\*'), + * ), + * 'email' => array( + * 'class' => 'yii\log\EmailTarget', + * 'levels' => array('error', 'warning'), + * 'emails' => array('admin@example.com'), + * ), + * ), + * ), + * ), + * ) + * ~~~ + * + * Each log target can have a name and can be referenced via the [[targets]] property + * as follows: + * + * ~~~ + * Yii::$app->log->targets['file']->enabled = false; + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class Router extends Component +{ + /** + * @var Target[] list of log target objects or configurations. If the latter, target objects will + * be created in [[init()]] by calling [[Yii::createObject()]] with the corresponding object configuration. + */ + public $targets = array(); + + /** + * Initializes this application component. + * This method is invoked when the Router component is created by the application. + * The method attaches the [[processLogs]] method to both the [[Logger::EVENT_FLUSH]] event + * and the [[Logger::EVENT_FINAL_FLUSH]] event. + */ + public function init() + { + parent::init(); + foreach ($this->targets as $name => $target) { + if (!$target instanceof Target) { + $this->targets[$name] = Yii::createObject($target); + } + } + Yii::getLogger()->router = $this; + } + + /** + * Dispatches log messages to [[targets]]. + * This method is called by [[Logger]] when its [[Logger::flush()]] method is called. + * It will forward the messages to each log target registered in [[targets]]. + * @param array $messages the messages to be processed + * @param boolean $final whether this is the final call during a request cycle + */ + public function dispatch($messages, $final = false) + { + foreach ($this->targets as $target) { + if ($target->enabled) { + $target->collect($messages, $final); + } + } + } +} diff --git a/framework/yii/log/Target.php b/framework/yii/log/Target.php new file mode 100644 index 0000000..a3276fc --- /dev/null +++ b/framework/yii/log/Target.php @@ -0,0 +1,245 @@ + + * @since 2.0 + */ +abstract class Target extends Component +{ + /** + * @var boolean whether to enable this log target. Defaults to true. + */ + public $enabled = true; + /** + * @var array list of message categories that this target is interested in. Defaults to empty, meaning all categories. + * You can use an asterisk at the end of a category so that the category may be used to + * match those categories sharing the same common prefix. For example, 'yii\db\*' will match + * categories starting with 'yii\db\', such as 'yii\db\Connection'. + */ + public $categories = array(); + /** + * @var array list of message categories that this target is NOT interested in. Defaults to empty, meaning no uninteresting messages. + * If this property is not empty, then any category listed here will be excluded from [[categories]]. + * You can use an asterisk at the end of a category so that the category can be used to + * match those categories sharing the same common prefix. For example, 'yii\db\*' will match + * categories starting with 'yii\db\', such as 'yii\db\Connection'. + * @see categories + */ + public $except = array(); + /** + * @var boolean whether to log a message containing the current user name and ID. Defaults to false. + * @see \yii\web\User + */ + public $logUser = false; + /** + * @var array list of the PHP predefined variables that should be logged in a message. + * Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be logged. + * Defaults to `array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER')`. + */ + public $logVars = array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'); + /** + * @var integer how many messages should be accumulated before they are exported. + * Defaults to 1000. Note that messages will always be exported when the application terminates. + * Set this property to be 0 if you don't want to export messages until the application terminates. + */ + public $exportInterval = 1000; + /** + * @var array the messages that are retrieved from the logger so far by this log target. + */ + public $messages = array(); + + private $_levels = 0; + + /** + * Exports log messages to a specific destination. + * Child classes must implement this method. + * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure + * of each message. + */ + abstract public function export($messages); + + /** + * Processes the given log messages. + * This method will filter the given messages with [[levels]] and [[categories]]. + * And if requested, it will also export the filtering result to specific medium (e.g. email). + * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure + * of each message. + * @param boolean $final whether this method is called at the end of the current application + */ + public function collect($messages, $final) + { + $this->messages = array_merge($this->messages, $this->filterMessages($messages)); + $count = count($this->messages); + if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) { + if (($context = $this->getContextMessage()) !== '') { + $this->messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME); + } + $this->export($this->messages); + $this->messages = array(); + } + } + + /** + * Generates the context information to be logged. + * The default implementation will dump user information, system variables, etc. + * @return string the context information. If an empty string, it means no context information. + */ + protected function getContextMessage() + { + $context = array(); + if ($this->logUser && ($user = Yii::$app->getComponent('user', false)) !== null) { + /** @var $user \yii\web\User */ + $context[] = 'User: ' . $user->getId(); + } + + foreach ($this->logVars as $name) { + if (!empty($GLOBALS[$name])) { + $context[] = "\${$name} = " . var_export($GLOBALS[$name], true); + } + } + + return implode("\n\n", $context); + } + + /** + * @return integer the message levels that this target is interested in. This is a bitmap of + * level values. Defaults to 0, meaning all available levels. + */ + public function getLevels() + { + return $this->_levels; + } + + /** + * Sets the message levels that this target is interested in. + * + * The parameter can be either an array of interested level names or an integer representing + * the bitmap of the interested level values. Valid level names include: 'error', + * 'warning', 'info', 'trace' and 'profile'; valid level values include: + * [[Logger::LEVEL_ERROR]], [[Logger::LEVEL_WARNING]], [[Logger::LEVEL_INFO]], + * [[Logger::LEVEL_TRACE]] and [[Logger::LEVEL_PROFILE]]. + * + * For example, + * + * ~~~ + * array('error', 'warning') + * // which is equivalent to: + * Logger::LEVEL_ERROR | Logger::LEVEL_WARNING + * ~~~ + * + * @param array|integer $levels message levels that this target is interested in. + * @throws InvalidConfigException if an unknown level name is given + */ + public function setLevels($levels) + { + static $levelMap = array( + 'error' => Logger::LEVEL_ERROR, + 'warning' => Logger::LEVEL_WARNING, + 'info' => Logger::LEVEL_INFO, + 'trace' => Logger::LEVEL_TRACE, + 'profile' => Logger::LEVEL_PROFILE, + ); + if (is_array($levels)) { + $this->_levels = 0; + foreach ($levels as $level) { + if (isset($levelMap[$level])) { + $this->_levels |= $levelMap[$level]; + } else { + throw new InvalidConfigException("Unrecognized level: $level"); + } + } + } else { + $this->_levels = $levels; + } + } + + /** + * Filters the given messages according to their categories and levels. + * @param array $messages messages to be filtered + * @return array the filtered messages. + * @see filterByCategory + * @see filterByLevel + */ + protected function filterMessages($messages) + { + $levels = $this->getLevels(); + + foreach ($messages as $i => $message) { + if ($levels && !($levels & $message[1])) { + unset($messages[$i]); + continue; + } + + $matched = empty($this->categories); + foreach ($this->categories as $category) { + if ($message[2] === $category || substr($category, -1) === '*' && strpos($message[2], rtrim($category, '*')) === 0) { + $matched = true; + break; + } + } + + if ($matched) { + foreach ($this->except as $category) { + $prefix = rtrim($category, '*'); + if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) { + $matched = false; + break; + } + } + } + + if (!$matched) { + unset($messages[$i]); + } + } + return $messages; + } + + /** + * Formats a log message. + * The message structure follows that in [[Logger::messages]]. + * @param array $message the log message to be formatted. + * @return string the formatted message + */ + public function formatMessage($message) + { + static $levels = array( + Logger::LEVEL_ERROR => 'error', + Logger::LEVEL_WARNING => 'warning', + Logger::LEVEL_INFO => 'info', + Logger::LEVEL_TRACE => 'trace', + Logger::LEVEL_PROFILE_BEGIN => 'profile begin', + Logger::LEVEL_PROFILE_END => 'profile end', + ); + list($text, $level, $category, $timestamp) = $message; + $level = isset($levels[$level]) ? $levels[$level] : 'unknown'; + if (!is_string($text)) { + $text = var_export($text, true); + } + $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1'; + return date('Y/m/d H:i:s', $timestamp) . " [$ip] [$level] [$category] $text\n"; + } +} diff --git a/framework/yii/logging/DbTarget.php b/framework/yii/logging/DbTarget.php deleted file mode 100644 index ce9d843..0000000 --- a/framework/yii/logging/DbTarget.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @since 2.0 - */ -class DbTarget extends Target -{ - /** - * @var Connection|string the DB connection object or the application component ID of the DB connection. - * After the DbTarget object is created, if you want to change this property, you should only assign it - * with a DB connection object. - */ - public $db = 'db'; - /** - * @var string name of the DB table to store cache content. - * The table should be pre-created as follows: - * - * ~~~ - * CREATE TABLE tbl_log ( - * id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, - * level INTEGER, - * category VARCHAR(255), - * log_time INTEGER, - * message TEXT, - * INDEX idx_log_level (level), - * INDEX idx_log_category (category) - * ) - * ~~~ - * - * Note that the 'id' column must be created as an auto-incremental column. - * The above SQL uses the MySQL syntax. If you are using other DBMS, you need - * to adjust it accordingly. For example, in PostgreSQL, it should be `id SERIAL PRIMARY KEY`. - * - * The indexes declared above are not required. They are mainly used to improve the performance - * of some queries about message levels and categories. Depending on your actual needs, you may - * want to create additional indexes (e.g. index on `log_time`). - */ - public $logTable = 'tbl_log'; - - /** - * Initializes the DbTarget component. - * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. - * @throws InvalidConfigException if [[db]] is invalid. - */ - public function init() - { - parent::init(); - if (is_string($this->db)) { - $this->db = Yii::$app->getComponent($this->db); - } - if (!$this->db instanceof Connection) { - throw new InvalidConfigException("DbTarget::db must be either a DB connection instance or the application component ID of a DB connection."); - } - } - - /** - * Stores log messages to DB. - * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure - * of each message. - */ - public function export($messages) - { - $tableName = $this->db->quoteTableName($this->logTable); - $sql = "INSERT INTO $tableName ([[level]], [[category]], [[log_time]], [[message]]) - VALUES (:level, :category, :log_time, :message)"; - $command = $this->db->createCommand($sql); - foreach ($messages as $message) { - $command->bindValues(array( - ':level' => $message[1], - ':category' => $message[2], - ':log_time' => $message[3], - ':message' => $message[0], - ))->execute(); - } - } -} diff --git a/framework/yii/logging/EmailTarget.php b/framework/yii/logging/EmailTarget.php deleted file mode 100644 index 94e2c00..0000000 --- a/framework/yii/logging/EmailTarget.php +++ /dev/null @@ -1,72 +0,0 @@ - - * @since 2.0 - */ -class EmailTarget extends Target -{ - /** - * @var array list of destination email addresses. - */ - public $emails = array(); - /** - * @var string email subject - */ - public $subject; - /** - * @var string email sent-from address - */ - public $sentFrom; - /** - * @var array list of additional headers to use when sending an email. - */ - public $headers = array(); - - /** - * Sends log messages to specified email addresses. - * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure - * of each message. - */ - public function export($messages) - { - $body = ''; - foreach ($messages as $message) { - $body .= $this->formatMessage($message); - } - $body = wordwrap($body, 70); - $subject = $this->subject === null ? \Yii::t('yii', 'Application Log') : $this->subject; - foreach ($this->emails as $email) { - $this->sendEmail($subject, $body, $email, $this->sentFrom, $this->headers); - } - } - - /** - * Sends an email. - * @param string $subject email subject - * @param string $body email body - * @param string $sentTo sent-to email address - * @param string $sentFrom sent-from email address - * @param array $headers additional headers to be used when sending the email - */ - protected function sendEmail($subject, $body, $sentTo, $sentFrom, $headers) - { - if ($sentFrom !== null) { - $headers[] = "From: {$sentFrom}"; - } - mail($sentTo, $subject, $body, implode("\r\n", $headers)); - } -} diff --git a/framework/yii/logging/FileTarget.php b/framework/yii/logging/FileTarget.php deleted file mode 100644 index 2db43b5..0000000 --- a/framework/yii/logging/FileTarget.php +++ /dev/null @@ -1,115 +0,0 @@ - - * @since 2.0 - */ -class FileTarget extends Target -{ - /** - * @var string log file path or path alias. If not set, it will use the "runtime/logs/app.log" file. - * The directory containing the log files will be automatically created if not existing. - */ - public $logFile; - /** - * @var integer maximum log file size, in kilo-bytes. Defaults to 10240, meaning 10MB. - */ - public $maxFileSize = 10240; // in KB - /** - * @var integer number of log files used for rotation. Defaults to 5. - */ - public $maxLogFiles = 5; - - - /** - * Initializes the route. - * This method is invoked after the route is created by the route manager. - */ - public function init() - { - parent::init(); - if ($this->logFile === null) { - $this->logFile = Yii::$app->getRuntimePath() . '/logs/app.log'; - } else { - $this->logFile = Yii::getAlias($this->logFile); - } - $logPath = dirname($this->logFile); - if (!is_dir($logPath)) { - @mkdir($logPath, 0777, true); - } - if ($this->maxLogFiles < 1) { - $this->maxLogFiles = 1; - } - if ($this->maxFileSize < 1) { - $this->maxFileSize = 1; - } - } - - /** - * Sends log messages to specified email addresses. - * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure - * of each message. - * @throws InvalidConfigException if unable to open the log file for writing - */ - public function export($messages) - { - $text = ''; - foreach ($messages as $message) { - $text .= $this->formatMessage($message); - } - if (($fp = @fopen($this->logFile, 'a')) === false) { - throw new InvalidConfigException("Unable to append to log file: {$this->logFile}"); - } - @flock($fp, LOCK_EX); - if (@filesize($this->logFile) > $this->maxFileSize * 1024) { - $this->rotateFiles(); - @flock($fp, LOCK_UN); - @fclose($fp); - @file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX); - } else { - @fwrite($fp, $text); - @flock($fp, LOCK_UN); - @fclose($fp); - } - } - - /** - * Rotates log files. - */ - protected function rotateFiles() - { - $file = $this->logFile; - for ($i = $this->maxLogFiles; $i > 0; --$i) { - $rotateFile = $file . '.' . $i; - if (is_file($rotateFile)) { - // suppress errors because it's possible multiple processes enter into this section - if ($i === $this->maxLogFiles) { - @unlink($rotateFile); - } else { - @rename($rotateFile, $file . '.' . ($i + 1)); - } - } - } - if (is_file($file)) { - @rename($file, $file . '.1'); // suppress errors because it's possible multiple processes enter into this section - } - } -} diff --git a/framework/yii/logging/Logger.php b/framework/yii/logging/Logger.php deleted file mode 100644 index 4bd6bcc..0000000 --- a/framework/yii/logging/Logger.php +++ /dev/null @@ -1,272 +0,0 @@ - - * @since 2.0 - */ -class Logger extends Component -{ - /** - * Error message level. An error message is one that indicates the abnormal termination of the - * application and may require developer's handling. - */ - const LEVEL_ERROR = 0x01; - /** - * Warning message level. A warning message is one that indicates some abnormal happens but - * the application is able to continue to run. Developers should pay attention to this message. - */ - const LEVEL_WARNING = 0x02; - /** - * Informational message level. An informational message is one that includes certain information - * for developers to review. - */ - const LEVEL_INFO = 0x04; - /** - * Tracing message level. An tracing message is one that reveals the code execution flow. - */ - const LEVEL_TRACE = 0x08; - /** - * Profiling message level. This indicates the message is for profiling purpose. - */ - const LEVEL_PROFILE = 0x40; - /** - * Profiling message level. This indicates the message is for profiling purpose. It marks the - * beginning of a profiling block. - */ - const LEVEL_PROFILE_BEGIN = 0x50; - /** - * Profiling message level. This indicates the message is for profiling purpose. It marks the - * end of a profiling block. - */ - const LEVEL_PROFILE_END = 0x60; - - - /** - * @var integer how many messages should be logged before they are flushed from memory and sent to targets. - * Defaults to 1000, meaning the [[flush]] method will be invoked once every 1000 messages logged. - * Set this property to be 0 if you don't want to flush messages until the application terminates. - * This property mainly affects how much memory will be taken by the logged messages. - * A smaller value means less memory, but will increase the execution time due to the overhead of [[flush()]]. - */ - public $flushInterval = 1000; - /** - * @var array logged messages. This property is mainly managed by [[log()]] and [[flush()]]. - * Each log message is of the following structure: - * - * ~~~ - * array( - * [0] => message (mixed, can be a string or some complex data, such as an exception object) - * [1] => level (integer) - * [2] => category (string) - * [3] => timestamp (float, obtained by microtime(true)) - * ) - * ~~~ - */ - public $messages = array(); - /** - * @var Router the log target router registered with this logger. - */ - public $router; - - - /** - * @var string - */ - private $_tag; - - - /** - * Initializes the logger by registering [[flush()]] as a shutdown function. - */ - public function init() - { - parent::init(); - register_shutdown_function(array($this, 'flush'), true); - } - - /** - * Logs a message with the given type and category. - * If `YII_DEBUG` is true and `YII_TRACE_LEVEL` is greater than 0, then additional - * call stack information about application code will be appended to the message. - * @param string $message the message to be logged. - * @param integer $level the level of the message. This must be one of the following: - * `Logger::LEVEL_ERROR`, `Logger::LEVEL_WARNING`, `Logger::LEVEL_INFO`, `Logger::LEVEL_TRACE`, - * `Logger::LEVEL_PROFILE_BEGIN`, `Logger::LEVEL_PROFILE_END`. - * @param string $category the category of the message. - */ - public function log($message, $level, $category = 'application') - { - $time = microtime(true); - if (YII_DEBUG && YII_TRACE_LEVEL > 0) { - $traces = debug_backtrace(); - $count = 0; - foreach ($traces as $trace) { - if (isset($trace['file'], $trace['line']) && strpos($trace['file'], YII_PATH) !== 0) { - $message .= "\nin {$trace['file']} ({$trace['line']})"; - if (++$count >= YII_TRACE_LEVEL) { - break; - } - } - } - } - $this->messages[] = array($message, $level, $category, $time); - if ($this->flushInterval > 0 && count($this->messages) >= $this->flushInterval) { - $this->flush(); - } - } - - /** - * Flushes log messages from memory to targets. - * This method will trigger an [[EVENT_FLUSH]] or [[EVENT_FINAL_FLUSH]] event depending on the $final value. - * @param boolean $final whether this is a final call during a request. - */ - public function flush($final = false) - { - if ($this->router) { - $this->router->dispatch($this->messages, $final); - } - $this->messages = array(); - } - - /** - * @return string a tag that uniquely identifies the current request. - */ - public function getTag() - { - if ($this->_tag === null) { - $this->_tag = date('Ymd-His', microtime(true)); - } - return $this->_tag; - } - - /** - * @param string $tag a tag that uniquely identifies the current request. - */ - public function setTag($tag) - { - $this->_tag = $tag; - } - - /** - * Returns the total elapsed time since the start of the current request. - * This method calculates the difference between now and the timestamp - * defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning - * of [[YiiBase]] class file. - * @return float the total elapsed time in seconds for current request. - */ - public function getElapsedTime() - { - return microtime(true) - YII_BEGIN_TIME; - } - - /** - * Returns the profiling results. - * - * By default, all profiling results will be returned. You may provide - * `$categories` and `$excludeCategories` as parameters to retrieve the - * results that you are interested in. - * - * @param array $categories list of categories that you are interested in. - * You can use an asterisk at the end of a category to do a prefix match. - * For example, 'yii\db\*' will match categories starting with 'yii\db\', - * such as 'yii\db\Connection'. - * @param array $excludeCategories list of categories that you want to exclude - * @return array the profiling results. Each array element has the following structure: - * `array($token, $category, $time)`. - */ - public function getProfiling($categories = array(), $excludeCategories = array()) - { - $timings = $this->calculateTimings(); - if (empty($categories) && empty($excludeCategories)) { - return $timings; - } - - foreach ($timings as $i => $timing) { - $matched = empty($categories); - foreach ($categories as $category) { - $prefix = rtrim($category, '*'); - if (strpos($timing[1], $prefix) === 0 && ($timing[1] === $category || $prefix !== $category)) { - $matched = true; - break; - } - } - - if ($matched) { - foreach ($excludeCategories as $category) { - $prefix = rtrim($category, '*'); - foreach ($timings as $i => $timing) { - if (strpos($timing[1], $prefix) === 0 && ($timing[1] === $category || $prefix !== $category)) { - $matched = false; - break; - } - } - } - } - - if (!$matched) { - unset($timings[$i]); - } - } - return array_values($timings); - } - - /** - * Returns the statistical results of DB queries. - * The results returned include the number of SQL statements executed and - * the total time spent. - * @return array the first element indicates the number of SQL statements executed, - * and the second element the total time spent in SQL execution. - */ - public function getDbProfiling() - { - $timings = $this->getProfiling(array('yii\db\Command::query', 'yii\db\Command::execute')); - $count = count($timings); - $time = 0; - foreach ($timings as $timing) { - $time += $timing[1]; - } - return array($count, $time); - } - - private function calculateTimings() - { - $timings = array(); - - $stack = array(); - foreach ($this->messages as $log) { - list($token, $level, $category, $timestamp) = $log; - if ($level == self::LEVEL_PROFILE_BEGIN) { - $stack[] = $log; - } elseif ($level == self::LEVEL_PROFILE_END) { - if (($last = array_pop($stack)) !== null && $last[0] === $token) { - $timings[] = array($token, $category, $timestamp - $last[3]); - } else { - throw new InvalidConfigException("Unmatched profiling block: $token"); - } - } - } - - $now = microtime(true); - while (($last = array_pop($stack)) !== null) { - $delta = $now - $last[3]; - $timings[] = array($last[0], $last[2], $delta); - } - - return $timings; - } -} diff --git a/framework/yii/logging/Router.php b/framework/yii/logging/Router.php deleted file mode 100644 index eae6de6..0000000 --- a/framework/yii/logging/Router.php +++ /dev/null @@ -1,98 +0,0 @@ - array('log'), - * 'components' => array( - * 'log' => array( - * 'class' => 'yii\logging\Router', - * 'targets' => array( - * 'file' => array( - * 'class' => 'yii\logging\FileTarget', - * 'levels' => array('trace', 'info'), - * 'categories' => array('yii\*'), - * ), - * 'email' => array( - * 'class' => 'yii\logging\EmailTarget', - * 'levels' => array('error', 'warning'), - * 'emails' => array('admin@example.com'), - * ), - * ), - * ), - * ), - * ) - * ~~~ - * - * Each log target can have a name and can be referenced via the [[targets]] property - * as follows: - * - * ~~~ - * Yii::$app->log->targets['file']->enabled = false; - * ~~~ - * - * @author Qiang Xue - * @since 2.0 - */ -class Router extends Component -{ - /** - * @var Target[] list of log target objects or configurations. If the latter, target objects will - * be created in [[init()]] by calling [[Yii::createObject()]] with the corresponding object configuration. - */ - public $targets = array(); - - /** - * Initializes this application component. - * This method is invoked when the Router component is created by the application. - * The method attaches the [[processLogs]] method to both the [[Logger::EVENT_FLUSH]] event - * and the [[Logger::EVENT_FINAL_FLUSH]] event. - */ - public function init() - { - parent::init(); - foreach ($this->targets as $name => $target) { - if (!$target instanceof Target) { - $this->targets[$name] = Yii::createObject($target); - } - } - Yii::getLogger()->router = $this; - } - - /** - * Dispatches log messages to [[targets]]. - * This method is called by [[Logger]] when its [[Logger::flush()]] method is called. - * It will forward the messages to each log target registered in [[targets]]. - * @param array $messages the messages to be processed - * @param boolean $final whether this is the final call during a request cycle - */ - public function dispatch($messages, $final = false) - { - foreach ($this->targets as $target) { - if ($target->enabled) { - $target->collect($messages, $final); - } - } - } -} diff --git a/framework/yii/logging/Target.php b/framework/yii/logging/Target.php deleted file mode 100644 index 7be7001..0000000 --- a/framework/yii/logging/Target.php +++ /dev/null @@ -1,245 +0,0 @@ - - * @since 2.0 - */ -abstract class Target extends Component -{ - /** - * @var boolean whether to enable this log target. Defaults to true. - */ - public $enabled = true; - /** - * @var array list of message categories that this target is interested in. Defaults to empty, meaning all categories. - * You can use an asterisk at the end of a category so that the category may be used to - * match those categories sharing the same common prefix. For example, 'yii\db\*' will match - * categories starting with 'yii\db\', such as 'yii\db\Connection'. - */ - public $categories = array(); - /** - * @var array list of message categories that this target is NOT interested in. Defaults to empty, meaning no uninteresting messages. - * If this property is not empty, then any category listed here will be excluded from [[categories]]. - * You can use an asterisk at the end of a category so that the category can be used to - * match those categories sharing the same common prefix. For example, 'yii\db\*' will match - * categories starting with 'yii\db\', such as 'yii\db\Connection'. - * @see categories - */ - public $except = array(); - /** - * @var boolean whether to log a message containing the current user name and ID. Defaults to false. - * @see \yii\web\User - */ - public $logUser = false; - /** - * @var array list of the PHP predefined variables that should be logged in a message. - * Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be logged. - * Defaults to `array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER')`. - */ - public $logVars = array('_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'); - /** - * @var integer how many messages should be accumulated before they are exported. - * Defaults to 1000. Note that messages will always be exported when the application terminates. - * Set this property to be 0 if you don't want to export messages until the application terminates. - */ - public $exportInterval = 1000; - /** - * @var array the messages that are retrieved from the logger so far by this log target. - */ - public $messages = array(); - - private $_levels = 0; - - /** - * Exports log messages to a specific destination. - * Child classes must implement this method. - * @param array $messages the messages to be exported. See [[Logger::messages]] for the structure - * of each message. - */ - abstract public function export($messages); - - /** - * Processes the given log messages. - * This method will filter the given messages with [[levels]] and [[categories]]. - * And if requested, it will also export the filtering result to specific medium (e.g. email). - * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure - * of each message. - * @param boolean $final whether this method is called at the end of the current application - */ - public function collect($messages, $final) - { - $this->messages = array_merge($this->messages, $this->filterMessages($messages)); - $count = count($this->messages); - if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) { - if (($context = $this->getContextMessage()) !== '') { - $this->messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME); - } - $this->export($this->messages); - $this->messages = array(); - } - } - - /** - * Generates the context information to be logged. - * The default implementation will dump user information, system variables, etc. - * @return string the context information. If an empty string, it means no context information. - */ - protected function getContextMessage() - { - $context = array(); - if ($this->logUser && ($user = Yii::$app->getComponent('user', false)) !== null) { - /** @var $user \yii\web\User */ - $context[] = 'User: ' . $user->getId(); - } - - foreach ($this->logVars as $name) { - if (!empty($GLOBALS[$name])) { - $context[] = "\${$name} = " . var_export($GLOBALS[$name], true); - } - } - - return implode("\n\n", $context); - } - - /** - * @return integer the message levels that this target is interested in. This is a bitmap of - * level values. Defaults to 0, meaning all available levels. - */ - public function getLevels() - { - return $this->_levels; - } - - /** - * Sets the message levels that this target is interested in. - * - * The parameter can be either an array of interested level names or an integer representing - * the bitmap of the interested level values. Valid level names include: 'error', - * 'warning', 'info', 'trace' and 'profile'; valid level values include: - * [[Logger::LEVEL_ERROR]], [[Logger::LEVEL_WARNING]], [[Logger::LEVEL_INFO]], - * [[Logger::LEVEL_TRACE]] and [[Logger::LEVEL_PROFILE]]. - * - * For example, - * - * ~~~ - * array('error', 'warning') - * // which is equivalent to: - * Logger::LEVEL_ERROR | Logger::LEVEL_WARNING - * ~~~ - * - * @param array|integer $levels message levels that this target is interested in. - * @throws InvalidConfigException if an unknown level name is given - */ - public function setLevels($levels) - { - static $levelMap = array( - 'error' => Logger::LEVEL_ERROR, - 'warning' => Logger::LEVEL_WARNING, - 'info' => Logger::LEVEL_INFO, - 'trace' => Logger::LEVEL_TRACE, - 'profile' => Logger::LEVEL_PROFILE, - ); - if (is_array($levels)) { - $this->_levels = 0; - foreach ($levels as $level) { - if (isset($levelMap[$level])) { - $this->_levels |= $levelMap[$level]; - } else { - throw new InvalidConfigException("Unrecognized level: $level"); - } - } - } else { - $this->_levels = $levels; - } - } - - /** - * Filters the given messages according to their categories and levels. - * @param array $messages messages to be filtered - * @return array the filtered messages. - * @see filterByCategory - * @see filterByLevel - */ - protected function filterMessages($messages) - { - $levels = $this->getLevels(); - - foreach ($messages as $i => $message) { - if ($levels && !($levels & $message[1])) { - unset($messages[$i]); - continue; - } - - $matched = empty($this->categories); - foreach ($this->categories as $category) { - if ($message[2] === $category || substr($category, -1) === '*' && strpos($message[2], rtrim($category, '*')) === 0) { - $matched = true; - break; - } - } - - if ($matched) { - foreach ($this->except as $category) { - $prefix = rtrim($category, '*'); - if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) { - $matched = false; - break; - } - } - } - - if (!$matched) { - unset($messages[$i]); - } - } - return $messages; - } - - /** - * Formats a log message. - * The message structure follows that in [[Logger::messages]]. - * @param array $message the log message to be formatted. - * @return string the formatted message - */ - public function formatMessage($message) - { - static $levels = array( - Logger::LEVEL_ERROR => 'error', - Logger::LEVEL_WARNING => 'warning', - Logger::LEVEL_INFO => 'info', - Logger::LEVEL_TRACE => 'trace', - Logger::LEVEL_PROFILE_BEGIN => 'profile begin', - Logger::LEVEL_PROFILE_END => 'profile end', - ); - list($text, $level, $category, $timestamp) = $message; - $level = isset($levels[$level]) ? $levels[$level] : 'unknown'; - if (!is_string($text)) { - $text = var_export($text, true); - } - $ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1'; - return date('Y/m/d H:i:s', $timestamp) . " [$ip] [$level] [$category] $text\n"; - } -}