diff options
Diffstat (limited to 'framework/logging/CLogger.php')
| -rw-r--r-- | framework/logging/CLogger.php | 302 |
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 © 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); + } +} |
