Commit 3fa22483 by Qiang Xue

refactored logging.

MVC WIP
parent 77e0be5c
......@@ -121,8 +121,8 @@ class YiiBase
*
* To import a class or a directory, one can use either path alias or class name (can be namespaced):
*
* - `@app/components/GoogleMap`: importing the `GoogleMap` class with a path alias;
* - `@app/components/*`: importing the whole `components` directory with a path alias;
* - `@application/components/GoogleMap`: importing the `GoogleMap` class with a path alias;
* - `@application/components/*`: importing the whole `components` directory with a path alias;
* - `GoogleMap`: importing the `GoogleMap` class with a class name. [[autoload()]] will be used
* when this class is used for the first time.
*
......@@ -322,12 +322,12 @@ class YiiBase
* the class. For example,
*
* - `\app\components\GoogleMap`: fully-qualified namespaced class.
* - `@app/components/GoogleMap`: an alias
* - `@application/components/GoogleMap`: an alias
*
* Below are some usage examples:
*
* ~~~
* $object = \Yii::createObject('@app/components/GoogleMap');
* $object = \Yii::createObject('@application/components/GoogleMap');
* $object = \Yii::createObject(array(
* 'class' => '\app\components\GoogleMap',
* 'apiKey' => 'xyz',
......
......@@ -9,6 +9,9 @@
namespace yii\base;
use Yii;
use yii\util\StringHelper;
/**
* Controller is the base class for classes containing controller logic.
*
......@@ -27,6 +30,10 @@ namespace yii\base;
*/
class Controller extends Component
{
const EVENT_AUTHORIZE = 'authorize';
const EVENT_BEFORE_ACTION = 'beforeAction';
const EVENT_AFTER_ACTION = 'afterAction';
/**
* @var string the ID of this controller
*/
......@@ -91,65 +98,138 @@ class Controller extends Component
}
/**
* Runs the controller with the specified action and parameters.
* @param Action|string $action the action to be executed. This can be either an action object
* or the ID of the action.
* Runs an action with the specified action ID and parameters.
* If the action ID is empty, the method will use [[defaultAction]].
* @param string $id the ID of the action to be executed.
* @param array $params the parameters (name-value pairs) to be passed to the action.
* If null, the result of [[getActionParams()]] will be used as action parameters.
* @return integer the exit status of the action. 0 means normal, other values mean abnormal.
* @see missingAction
* @return integer the status of the action execution. 0 means normal, other values mean abnormal.
* @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
* @see createAction
*/
public function run($action, $params = null)
public function runAction($id, $params = array())
{
if (is_string($action)) {
if (($a = $this->createAction($action)) !== null) {
$action = $a;
} else {
$this->missingAction($action);
return 1;
}
if ($id === '') {
$id = $this->defaultAction;
}
$priorAction = $this->action;
$this->action = $action;
$action = $this->createAction($id);
if ($action !== null) {
$oldAction = $this->action;
$this->action = $action;
if ($this->authorize($action) && $this->beforeAction($action)) {
if ($params === null) {
$params = $this->getActionParams();
if ($this->authorize($action) && $this->beforeAction($action)) {
$status = $action->runWithParams($params);
$this->afterAction($action);
} else {
$status = 1;
}
$status = $action->runWithParams($params);
$this->afterAction($action);
$this->action = $oldAction;
return $status;
} else {
$status = 1;
throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
}
}
$this->action = $priorAction;
/**
* Runs a request specified in terms of a route.
* The route can be either an ID of an action within this controller or a complete route consisting
* of module IDs, controller ID and action ID. If the route starts with a slash '/', the parsing of
* the route will start from the application; otherwise, it will start from the parent module of this controller.
* @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'.
* @param array $params the parameters to be passed to the action.
* @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal.
* @see runAction
* @see forward
*/
public function run($route, $params = array())
{
$pos = strpos($route, '/');
if ($pos === false) {
return $this->runAction($route, $params);
} elseif ($pos > 0) {
return $this->module->runAction($route, $params);
} else {
return \Yii::$application->runAction($route, $params);
}
}
return $status;
/**
* Forwards the current execution flow to handle a new request specified by a route.
* The only difference between this method and [[run()]] is that after calling this method,
* the application will exit.
* @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'.
* @param array $params the parameters to be passed to the action.
* @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal.
* @see run
*/
public function forward($route, $params = array())
{
$status = $this->run($route, $params);
exit($status);
}
/**
* Creates the action instance based on the action ID.
* The action can be either an inline action or an object.
* The latter is created by looking up the action map specified in [[actions]].
* @param string $actionID ID of the action. If empty, it will take the value of [[defaultAction]].
* @return Action the action instance, null if the action does not exist.
* @see actions
* Creates an action based on the given action ID.
* The method first checks if the action ID has been declared in [[actions()]]. If so,
* it will use the configuration declared there to create the action object.
* If not, it will look for a controller method whose name is in the format of `actionXyz`
* where `Xyz` stands for the action ID. If found, an [[InlineAction]] representing that
* method will be created and returned.
* @param string $id the action ID
* @return Action the newly created action instance. Null if the ID doesn't resolve into any action.
*/
public function createAction($actionID)
public function createAction($id)
{
if ($actionID === '') {
$actionID = $this->defaultAction;
}
$actions = $this->actions();
if (isset($actions[$actionID])) {
return \Yii::createObject($actions[$actionID], $actionID, $this);
} elseif (method_exists($this, 'action' . $actionID)) {
return new InlineAction($actionID, $this);
} else {
return null;
$actionMap = $this->actions();
if (isset($actionMap[$id])) {
return Yii::createObject($actionMap[$id], $id, $this);
} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
$methodName = 'action' . StringHelper::id2camel($id);
if (method_exists($this, $methodName)) {
$method = new \ReflectionMethod($this, $methodName);
if ($method->getName() === $methodName) {
return new InlineAction($id, $this);
}
}
}
return null;
}
/**
* This method is invoked when checking the access for the action to be executed.
* @param Action $action the action to be executed.
* @return boolean whether the action is allowed to be executed.
*/
public function authorize($action)
{
$event = new ActionEvent($action);
$this->trigger(self::EVENT_AUTHORIZE, $event);
return $event->isValid;
}
/**
* This method is invoked right before an action is to be executed (after all possible filters.)
* You may override this method to do last-minute preparation for the action.
* @param Action $action the action to be executed.
* @return boolean whether the action should continue to be executed.
*/
public function beforeAction($action)
{
$event = new ActionEvent($action);
$this->trigger(self::EVENT_BEFORE_ACTION, $event);
return $event->isValid;
}
/**
* This method is invoked right after an action is executed.
* You may override this method to do some postprocessing for the action.
* @param Action $action the action just executed.
*/
public function afterAction($action)
{
$this->trigger(self::EVENT_AFTER_ACTION, new ActionEvent($action));
}
/**
......@@ -217,67 +297,6 @@ class Controller extends Component
return $this->action !== null ? $this->getUniqueId() . '/' . $this->action->id : $this->getUniqueId();
}
/**
* Processes the request using another controller action.
* @param string $route the route of the new controller action. This can be an action ID, or a complete route
* with module ID (optional in the current module), controller ID and action ID. If the former,
* the action is assumed to be located within the current controller.
* @param array $params the parameters to be passed to the action.
* If null, the result of [[getActionParams()]] will be used as action parameters.
* Note that the parameters must be name-value pairs with the names corresponding to
* the parameter names as declared by the action.
* @param boolean $exit whether to end the application after this call. Defaults to true.
*/
public function forward($route, $params = array(), $exit = true)
{
if (strpos($route, '/') === false) {
$status = $this->run($route, $params);
} else {
if ($route[0] !== '/' && !$this->module instanceof Application) {
$route = '/' . $this->module->getUniqueId() . '/' . $route;
}
$status = \Yii::$application->runController($route, $params);
}
if ($exit) {
\Yii::$application->end($status);
}
}
/**
* This method is invoked when checking the access for the action to be executed.
* @param Action $action the action to be executed.
* @return boolean whether the action is allowed to be executed.
*/
public function authorize($action)
{
$event = new ActionEvent($action);
$this->trigger(__METHOD__, $event);
return $event->isValid;
}
/**
* This method is invoked right before an action is to be executed (after all possible filters.)
* You may override this method to do last-minute preparation for the action.
* @param Action $action the action to be executed.
* @return boolean whether the action should continue to be executed.
*/
public function beforeAction($action)
{
$event = new ActionEvent($action);
$this->trigger(__METHOD__, $event);
return $event->isValid;
}
/**
* This method is invoked right after an action is executed.
* You may override this method to do some postprocessing for the action.
* @param Action $action the action just executed.
*/
public function afterAction($action)
{
$this->trigger(__METHOD__, new ActionEvent($action));
}
public function render($view, $params = array())
{
return $this->createView()->render($view, $params);
......
<?php
/**
* InvalidRouteException class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\base;
/**
* InvalidRouteException represents an exception caused by an invalid route.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class InvalidRouteException extends \Exception
{
}
......@@ -97,7 +97,7 @@ class View extends Component
* To determine which view file should be rendered, the method calls [[findViewFile()]] which
* will search in the directories as specified by [[basePath]].
*
* View name can be a path alias representing an absolute file path (e.g. `@app/views/layout/index`),
* View name can be a path alias representing an absolute file path (e.g. `@application/views/layout/index`),
* or a path relative to [[basePath]]. The file suffix is optional and defaults to `.php` if not given
* in the view name.
*
......
......@@ -80,7 +80,7 @@ class Widget extends Component
* To determine which view file should be rendered, the method calls [[findViewFile()]] which
* will search in the directories as specified by [[basePath]].
*
* View name can be a path alias representing an absolute file path (e.g. `@app/views/layout/index`),
* View name can be a path alias representing an absolute file path (e.g. `@application/views/layout/index`),
* or a path relative to [[basePath]]. The file suffix is optional and defaults to `.php` if not given
* in the view name.
*
......
......@@ -89,16 +89,17 @@ class DbTarget extends Target
}
/**
* Stores log [[messages]] to DB.
* @param boolean $final whether this method is called at the end of the current application
* Stores log messages to DB.
* @param array $messages the messages to be exported. See [[Logger::messages]] for the structure
* of each message.
*/
public function exportMessages($final)
public function export($messages)
{
$db = $this->getDb();
$tableName = $db->quoteTableName($this->tableName);
$sql = "INSERT INTO $tableName (level, category, log_time, message) VALUES (:level, :category, :log_time, :message)";
$command = $db->createCommand($sql);
foreach ($this->messages as $message) {
foreach ($messages as $message) {
$command->bindValues(array(
':level' => $message[1],
':category' => $message[2],
......
......@@ -39,13 +39,14 @@ class EmailTarget extends Target
public $headers = array();
/**
* Sends log [[messages]] to specified email addresses.
* @param boolean $final whether this method is called at the end of the current application
* 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 exportMessages($final)
public function export($messages)
{
$body = '';
foreach ($this->messages as $message) {
foreach ($messages as $message) {
$body .= $this->formatMessage($message);
}
$body = wordwrap($body, 70);
......
......@@ -65,19 +65,28 @@ class FileTarget extends Target
}
/**
* Sends log [[messages]] to specified email addresses.
* @param boolean $final whether this method is called at the end of the current application
* 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 exportMessages($final)
public function export($messages)
{
$text = '';
foreach ($messages as $message) {
$text .= $this->formatMessage($message);
}
$fp = @fopen($this->logFile, 'a');
@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);
}
$messages = array();
foreach ($this->messages as $message) {
$messages[] = $this->formatMessage($message);
}
@file_put_contents($this->logFile, implode('', $messages), FILE_APPEND | LOCK_EX);
}
/**
......
......@@ -8,16 +8,13 @@
*/
namespace yii\logging;
use yii\base\Event;
use yii\base\Exception;
use yii\base\InvalidConfigException;
/**
* Logger records logged messages in memory.
*
* When [[flushInterval()]] is reached or when application terminates, it will
* call [[flush()]] to send logged messages to different log targets, such as
* file, email, Web.
* 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 <qiang.xue@gmail.com>
* @since 2.0
......@@ -25,15 +22,6 @@ use yii\base\Exception;
class Logger extends \yii\base\Component
{
/**
* @event Event an event that is triggered when [[flush()]] is called.
*/
const EVENT_FLUSH = 'flush';
/**
* @event Event an event that is triggered when [[flush()]] is called at the end of application.
*/
const EVENT_FINAL_FLUSH = 'finalFlush';
/**
* Error message level. An error message is one that indicates the abnormal termination of the
* application and may require developer's handling.
*/
......@@ -82,7 +70,7 @@ class Logger extends \yii\base\Component
*
* ~~~
* array(
* [0] => message (mixed)
* [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))
......@@ -90,6 +78,10 @@ class Logger extends \yii\base\Component
* ~~~
*/
public $messages = array();
/**
* @var Router the log target router registered with this logger.
*/
public $router;
/**
* Initializes the logger by registering [[flush()]] as a shutdown function.
......@@ -138,7 +130,9 @@ class Logger extends \yii\base\Component
*/
public function flush($final = false)
{
$this->trigger($final ? self::EVENT_FINAL_FLUSH : self::EVENT_FLUSH);
if ($this->router) {
$this->router->dispatch($this->messages, $final);
}
$this->messages = array();
}
......@@ -149,7 +143,7 @@ class Logger extends \yii\base\Component
* of [[YiiBase]] class file.
* @return float the total elapsed time in seconds for current request.
*/
public function getExecutionTime()
public function getElapsedTime()
{
return microtime(true) - YII_BEGIN_TIME;
}
......@@ -218,7 +212,7 @@ class Logger extends \yii\base\Component
if (($last = array_pop($stack)) !== null && $last[0] === $token) {
$timings[] = array($token, $category, $timestamp - $last[3]);
} else {
throw new Exception("Unmatched profiling block: $token");
throw new InvalidConfigException("Unmatched profiling block: $token");
}
}
}
......@@ -231,5 +225,4 @@ class Logger extends \yii\base\Component
return $timings;
}
}
......@@ -81,26 +81,21 @@ class Router extends Component
$this->targets[$name] = Yii::createObject($target);
}
}
Yii::getLogger()->on(Logger::EVENT_FLUSH, array($this, 'processMessages'));
Yii::getLogger()->on(Logger::EVENT_FINAL_FLUSH, array($this, 'processMessages'));
Yii::getLogger()->router = $this;
}
/**
* Retrieves and processes log messages from the system logger.
* This method mainly serves the event handler to the [[Logger::EVENT_FLUSH]] event
* and the [[Logger::EVENT_FINAL_FLUSH]] event.
* It will retrieve the available log messages from the [[Yii::getLogger()|system logger]]
* and invoke the registered [[targets|log targets]] to do the actual processing.
* @param \yii\base\Event $event event parameter
* 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 processMessages($event)
public function dispatch($messages, $final = false)
{
$messages = Yii::getLogger()->messages;
$final = $event->name === Logger::EVENT_FINAL_FLUSH;
foreach ($this->targets as $target) {
if ($target->enabled) {
$target->processMessages($messages, $final);
$target->collect($messages, $final);
}
}
}
......
......@@ -50,15 +50,6 @@ abstract class Target extends \yii\base\Component
*/
public $except = array();
/**
* @var boolean whether to prefix each log message with the current session ID. Defaults to false.
*/
public $prefixSession = false;
/**
* @var boolean whether to prefix each log message with the current user name and ID. Defaults to false.
* @see \yii\web\User
*/
public $prefixUser = false;
/**
* @var boolean whether to log a message containing the current user name and ID. Defaults to false.
* @see \yii\web\User
*/
......@@ -77,19 +68,18 @@ abstract class Target extends \yii\base\Component
public $exportInterval = 1000;
/**
* @var array the messages that are retrieved from the logger so far by this log target.
* @see autoExport
*/
public $messages = array();
private $_messages = array();
private $_levels = 0;
/**
* Exports log messages to a specific destination.
* Child classes must implement this method. Note that you may need
* to clean up [[messages]] in this method to avoid re-exporting messages.
* @param boolean $final whether this method is called at the end of the current application
* 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 exportMessages($final);
abstract public function export($messages);
/**
* Processes the given log messages.
......@@ -99,45 +89,16 @@ abstract class Target extends \yii\base\Component
* of each message.
* @param boolean $final whether this method is called at the end of the current application
*/
public function processMessages($messages, $final)
public function collect($messages, $final)
{
$messages = $this->filterMessages($messages);
$this->messages = array_merge($this->messages, $messages);
$count = count($this->messages);
$this->_messages = array($this->_messages, $this->filterMessages($messages));
$count = count($this->_messages);
if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) {
$this->prepareExport($final);
$this->exportMessages($final);
$this->messages = array();
}
}
/**
* Prepares the [[messages]] for exporting.
* This method will modify each message by prepending extra information
* if [[prefixSession]] and/or [[prefixUser]] are set true.
* It will also add an additional message showing context information if
* [[logUser]] and/or [[logVars]] are set.
* @param boolean $final whether this method is called at the end of the current application
*/
protected function prepareExport($final)
{
$prefix = array();
if ($this->prefixSession && ($id = session_id()) !== '') {
$prefix[] = "[$id]";
}
if ($this->prefixUser && ($user = \Yii::$application->getComponent('user', false)) !== null) {
$prefix[] = '[' . $user->getName() . ']';
$prefix[] = '[' . $user->getId() . ']';
}
if ($prefix !== array()) {
$prefix = implode(' ', $prefix);
foreach ($this->messages as $i => $message) {
$this->messages[$i][0] = $prefix . ' ' . $this->messages[$i][0];
if (($context = $this->getContextMessage()) !== '') {
$this->_messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME);
}
}
if ($final && ($context = $this->getContextMessage()) !== '') {
$this->messages[] = array($context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME);
$this->export($this->_messages);
$this->_messages = array();
}
}
......@@ -164,7 +125,7 @@ abstract class Target extends \yii\base\Component
/**
* @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.
* level values. Defaults to 0, meaning all available levels.
*/
public function getLevels()
{
......
......@@ -10,6 +10,7 @@
namespace yii\util;
use yii\base\Exception;
use yii\base\InvalidConfigException;
/**
* Filesystem helper
......@@ -37,7 +38,7 @@ class FileHelper
* If the given path does not refer to an existing directory, an exception will be thrown.
* @param string $path the given path. This can also be a path alias.
* @return string the normalized path
* @throws Exception if the path does not refer to an existing directory.
* @throws InvalidConfigException if the path does not refer to an existing directory.
*/
public static function ensureDirectory($path)
{
......@@ -45,7 +46,7 @@ class FileHelper
if ($p !== false && ($p = realpath($p)) !== false && is_dir($p)) {
return $p;
} else {
throw new Exception('Directory does not exist: ' . $path);
throw new InvalidConfigException('Directory does not exist: ' . $path);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment