summaryrefslogtreecommitdiff
path: root/framework/logging/CLogger.php
diff options
context:
space:
mode:
Diffstat (limited to 'framework/logging/CLogger.php')
-rw-r--r--framework/logging/CLogger.php302
1 files changed, 302 insertions, 0 deletions
diff --git a/framework/logging/CLogger.php b/framework/logging/CLogger.php
new file mode 100644
index 0000000..2851571
--- /dev/null
+++ b/framework/logging/CLogger.php
@@ -0,0 +1,302 @@
+<?php
+/**
+ * CLogger class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+/**
+ * CLogger records log messages in memory.
+ *
+ * CLogger implements the methods to retrieve the messages with
+ * various filter conditions, including log levels and log categories.
+ *
+ * @property array $logs List of messages. Each array elements represents one message
+ * with the following structure:
+ * array(
+ * [0] => message (string)
+ * [1] => level (string)
+ * [2] => category (string)
+ * [3] => timestamp (float, obtained by microtime(true));.
+ * @property float $executionTime The total time for serving the current request.
+ * @property integer $memoryUsage Memory usage of the application (in bytes).
+ * @property array $profilingResults The profiling results.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id: CLogger.php 3515 2011-12-28 12:29:24Z mdomba $
+ * @package system.logging
+ * @since 1.0
+ */
+class CLogger extends CComponent
+{
+ const LEVEL_TRACE='trace';
+ const LEVEL_WARNING='warning';
+ const LEVEL_ERROR='error';
+ const LEVEL_INFO='info';
+ const LEVEL_PROFILE='profile';
+
+ /**
+ * @var integer how many messages should be logged before they are flushed to destinations.
+ * Defaults to 10,000, meaning for every 10,000 messages, the {@link flush} method will be
+ * automatically invoked once. If this is 0, it means messages will never be flushed automatically.
+ * @since 1.1.0
+ */
+ public $autoFlush=10000;
+ /**
+ * @var boolean this property will be passed as the parameter to {@link flush()} when it is
+ * called in {@link log()} due to the limit of {@link autoFlush} being reached.
+ * By default, this property is false, meaning the filtered messages are still kept in the memory
+ * by each log route after calling {@link flush()}. If this is true, the filtered messages
+ * will be written to the actual medium each time {@link flush()} is called within {@link log()}.
+ * @since 1.1.8
+ */
+ public $autoDump=false;
+ /**
+ * @var array log messages
+ */
+ private $_logs=array();
+ /**
+ * @var integer number of log messages
+ */
+ private $_logCount=0;
+ /**
+ * @var array log levels for filtering (used when filtering)
+ */
+ private $_levels;
+ /**
+ * @var array log categories for filtering (used when filtering)
+ */
+ private $_categories;
+ /**
+ * @var array the profiling results (category, token => time in seconds)
+ */
+ private $_timings;
+ /**
+ * @var boolean if we are processing the log or still accepting new log messages
+ * @since 1.1.9
+ */
+ private $_processing = false;
+
+ /**
+ * Logs a message.
+ * Messages logged by this method may be retrieved back via {@link getLogs}.
+ * @param string $message message to be logged
+ * @param string $level level of the message (e.g. 'Trace', 'Warning', 'Error'). It is case-insensitive.
+ * @param string $category category of the message (e.g. 'system.web'). It is case-insensitive.
+ * @see getLogs
+ */
+ public function log($message,$level='info',$category='application')
+ {
+ $this->_logs[]=array($message,$level,$category,microtime(true));
+ $this->_logCount++;
+ if($this->autoFlush>0 && $this->_logCount>=$this->autoFlush && !$this->_processing)
+ {
+ $this->_processing=true;
+ $this->flush($this->autoDump);
+ $this->_processing=false;
+ }
+ }
+
+ /**
+ * Retrieves log messages.
+ *
+ * Messages may be filtered by log levels and/or categories.
+ * A level filter is specified by a list of levels separated by comma or space
+ * (e.g. 'trace, error'). A category filter is similar to level filter
+ * (e.g. 'system, system.web'). A difference is that in category filter
+ * you can use pattern like 'system.*' to indicate all categories starting
+ * with 'system'.
+ *
+ * If you do not specify level filter, it will bring back logs at all levels.
+ * The same applies to category filter.
+ *
+ * Level filter and category filter are combinational, i.e., only messages
+ * satisfying both filter conditions will be returned.
+ *
+ * @param string $levels level filter
+ * @param string $categories category filter
+ * @return array list of messages. Each array elements represents one message
+ * with the following structure:
+ * array(
+ * [0] => message (string)
+ * [1] => level (string)
+ * [2] => category (string)
+ * [3] => timestamp (float, obtained by microtime(true));
+ */
+ public function getLogs($levels='',$categories='')
+ {
+ $this->_levels=preg_split('/[\s,]+/',strtolower($levels),-1,PREG_SPLIT_NO_EMPTY);
+ $this->_categories=preg_split('/[\s,]+/',strtolower($categories),-1,PREG_SPLIT_NO_EMPTY);
+ if(empty($levels) && empty($categories))
+ return $this->_logs;
+ else if(empty($levels))
+ return array_values(array_filter(array_filter($this->_logs,array($this,'filterByCategory'))));
+ else if(empty($categories))
+ return array_values(array_filter(array_filter($this->_logs,array($this,'filterByLevel'))));
+ else
+ {
+ $ret=array_values(array_filter(array_filter($this->_logs,array($this,'filterByLevel'))));
+ return array_values(array_filter(array_filter($ret,array($this,'filterByCategory'))));
+ }
+ }
+
+ /**
+ * Filter function used by {@link getLogs}
+ * @param array $value element to be filtered
+ * @return array valid log, false if not.
+ */
+ private function filterByCategory($value)
+ {
+ foreach($this->_categories as $category)
+ {
+ $cat=strtolower($value[2]);
+ if($cat===$category || (($c=rtrim($category,'.*'))!==$category && strpos($cat,$c)===0))
+ return $value;
+ }
+ return false;
+ }
+
+ /**
+ * Filter function used by {@link getLogs}
+ * @param array $value element to be filtered
+ * @return array valid log, false if not.
+ */
+ private function filterByLevel($value)
+ {
+ return in_array(strtolower($value[1]),$this->_levels)?$value:false;
+ }
+
+ /**
+ * Returns the total time for serving the current request.
+ * This method calculates the difference between now and the timestamp
+ * defined by constant YII_BEGIN_TIME.
+ * To estimate the execution time more accurately, the constant should
+ * be defined as early as possible (best at the beginning of the entry script.)
+ * @return float the total time for serving the current request.
+ */
+ public function getExecutionTime()
+ {
+ return microtime(true)-YII_BEGIN_TIME;
+ }
+
+ /**
+ * Returns the memory usage of the current application.
+ * This method relies on the PHP function memory_get_usage().
+ * If it is not available, the method will attempt to use OS programs
+ * to determine the memory usage. A value 0 will be returned if the
+ * memory usage can still not be determined.
+ * @return integer memory usage of the application (in bytes).
+ */
+ public function getMemoryUsage()
+ {
+ if(function_exists('memory_get_usage'))
+ return memory_get_usage();
+ else
+ {
+ $output=array();
+ if(strncmp(PHP_OS,'WIN',3)===0)
+ {
+ exec('tasklist /FI "PID eq ' . getmypid() . '" /FO LIST',$output);
+ return isset($output[5])?preg_replace('/[\D]/','',$output[5])*1024 : 0;
+ }
+ else
+ {
+ $pid=getmypid();
+ exec("ps -eo%mem,rss,pid | grep $pid", $output);
+ $output=explode(" ",$output[0]);
+ return isset($output[1]) ? $output[1]*1024 : 0;
+ }
+ }
+ }
+
+ /**
+ * Returns the profiling results.
+ * The results may be filtered by token and/or category.
+ * If no filter is specified, the returned results would be an array with each element
+ * being array($token,$category,$time).
+ * If a filter is specified, the results would be an array of timings.
+ * @param string $token token filter. Defaults to null, meaning not filtered by token.
+ * @param string $category category filter. Defaults to null, meaning not filtered by category.
+ * @param boolean $refresh whether to refresh the internal timing calculations. If false,
+ * only the first time calling this method will the timings be calculated internally.
+ * @return array the profiling results.
+ */
+ public function getProfilingResults($token=null,$category=null,$refresh=false)
+ {
+ if($this->_timings===null || $refresh)
+ $this->calculateTimings();
+ if($token===null && $category===null)
+ return $this->_timings;
+ $results=array();
+ foreach($this->_timings as $timing)
+ {
+ if(($category===null || $timing[1]===$category) && ($token===null || $timing[0]===$token))
+ $results[]=$timing[2];
+ }
+ return $results;
+ }
+
+ private function calculateTimings()
+ {
+ $this->_timings=array();
+
+ $stack=array();
+ foreach($this->_logs as $log)
+ {
+ if($log[1]!==CLogger::LEVEL_PROFILE)
+ continue;
+ list($message,$level,$category,$timestamp)=$log;
+ if(!strncasecmp($message,'begin:',6))
+ {
+ $log[0]=substr($message,6);
+ $stack[]=$log;
+ }
+ else if(!strncasecmp($message,'end:',4))
+ {
+ $token=substr($message,4);
+ if(($last=array_pop($stack))!==null && $last[0]===$token)
+ {
+ $delta=$log[3]-$last[3];
+ $this->_timings[]=array($message,$category,$delta);
+ }
+ else
+ throw new CException(Yii::t('yii','CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.',
+ array('{token}'=>$token)));
+ }
+ }
+
+ $now=microtime(true);
+ while(($last=array_pop($stack))!==null)
+ {
+ $delta=$now-$last[3];
+ $this->_timings[]=array($last[0],$last[2],$delta);
+ }
+ }
+
+ /**
+ * Removes all recorded messages from the memory.
+ * This method will raise an {@link onFlush} event.
+ * The attached event handlers can process the log messages before they are removed.
+ * @param boolean $dumpLogs whether to process the logs immediately as they are passed to log route
+ * @since 1.1.0
+ */
+ public function flush($dumpLogs=false)
+ {
+ $this->onFlush(new CEvent($this, array('dumpLogs'=>$dumpLogs)));
+ $this->_logs=array();
+ $this->_logCount=0;
+ }
+
+ /**
+ * Raises an <code>onFlush</code> event.
+ * @param CEvent $event the event parameter
+ * @since 1.1.0
+ */
+ public function onFlush($event)
+ {
+ $this->raiseEvent('onFlush', $event);
+ }
+}