Logger.php 7.31 KB
Newer Older
w  
Qiang Xue committed
1 2 3 4 5
<?php
/**
 * Logger class file
 *
 * @link http://www.yiiframework.com/
Qiang Xue committed
6
 * @copyright Copyright &copy; 2008 Yii Software LLC
w  
Qiang Xue committed
7 8 9 10 11
 * @license http://www.yiiframework.com/license/
 */

namespace yii\logging;

12
use yii\base\Event;
Qiang Xue committed
13
use yii\base\Exception;
14

w  
Qiang Xue committed
15 16 17
/**
 * Logger records logged messages in memory.
 *
Qiang Xue committed
18 19
 * When [[flushInterval()]] is reached or when application terminates, it will
 * call [[flush()]] to send logged messages to different log targets, such as
w  
Qiang Xue committed
20 21 22 23 24 25 26
 * file, email, Web.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class Logger extends \yii\base\Component
{
27
	/**
Qiang Xue committed
28
	 * @event Event an event that is triggered when [[flush()]] is called.
29 30
	 */
	const EVENT_FLUSH = 'flush';
Qiang Xue committed
31 32 33 34
	/**
	 * @event Event an event that is triggered when [[flush()]] is called at the end of application.
	 */
	const EVENT_FINAL_FLUSH = 'finalFlush';
35

Qiang Xue committed
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
	/**
	 * 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;

w  
Qiang Xue committed
70 71 72 73 74 75

	/**
	 * @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.
Qiang Xue committed
76
	 * A smaller value means less memory, but will increase the execution time due to the overhead of [[flush()]].
w  
Qiang Xue committed
77 78 79
	 */
	public $flushInterval = 1000;
	/**
Qiang Xue committed
80
	 * @var array logged messages. This property is mainly managed by [[log()]] and [[flush()]].
w  
Qiang Xue committed
81 82 83 84
	 * Each log message is of the following structure:
	 *
	 * ~~~
	 * array(
Qiang Xue committed
85
	 *   [0] => message (mixed)
Qiang Xue committed
86
	 *   [1] => level (integer)
w  
Qiang Xue committed
87 88 89 90
	 *   [2] => category (string)
	 *   [3] => timestamp (float, obtained by microtime(true))
	 * )
	 * ~~~
w  
Qiang Xue committed
91 92 93
	 */
	public $messages = array();

Qiang Xue committed
94 95 96 97 98 99 100 101 102
	/**
	 * Initializes the logger by registering [[flush()]] as a shutdown function.
	 */
	public function init()
	{
		parent::init();
		register_shutdown_function(array($this, 'flush'), true);
	}

w  
Qiang Xue committed
103 104 105 106 107
	/**
	 * 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.
Qiang Xue committed
108 109 110
	 * @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`.
w  
Qiang Xue committed
111 112
	 * @param string $category the category of the message.
	 */
Qiang Xue committed
113
	public function log($message, $level, $category = 'application')
w  
Qiang Xue committed
114
	{
Qiang Xue committed
115 116
		$time = microtime(true);
		if (YII_DEBUG && YII_TRACE_LEVEL > 0) {
w  
Qiang Xue committed
117 118 119
			$traces = debug_backtrace();
			$count = 0;
			foreach ($traces as $trace) {
Qiang Xue committed
120
				if (isset($trace['file'], $trace['line']) && strpos($trace['file'], YII_PATH) !== 0) {
Qiang Xue committed
121
					$message .= "\nin {$trace['file']} ({$trace['line']})";
w  
Qiang Xue committed
122 123 124 125 126 127
					if (++$count >= YII_TRACE_LEVEL) {
						break;
					}
				}
			}
		}
Qiang Xue committed
128
		$this->messages[] = array($message, $level, $category, $time);
129
		if ($this->flushInterval > 0 && count($this->messages) >= $this->flushInterval) {
Qiang Xue committed
130
			$this->flush();
w  
Qiang Xue committed
131 132 133 134
		}
	}

	/**
135
	 * Flushes log messages from memory to targets.
Qiang Xue committed
136
	 * This method will trigger an [[EVENT_FLUSH]] or [[EVENT_FINAL_FLUSH]] event depending on the $final value.
Qiang Xue committed
137
	 * @param boolean $final whether this is a final call during a request.
w  
Qiang Xue committed
138
	 */
Qiang Xue committed
139
	public function flush($final = false)
w  
Qiang Xue committed
140
	{
Qiang Xue committed
141
		$this->trigger($final ? self::EVENT_FINAL_FLUSH : self::EVENT_FLUSH);
w  
Qiang Xue committed
142
		$this->messages = array();
w  
Qiang Xue committed
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
	}

	/**
	 * 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 getExecutionTime()
	{
		return microtime(true) - YII_BEGIN_TIME;
	}

	/**
	 * Returns the profiling results.
w  
Qiang Xue committed
159 160 161 162 163 164 165 166
	 *
	 * 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\',
Qiang Xue committed
167
	 * such as 'yii\db\Connection'.
168
	 * @param array $excludeCategories list of categories that you want to exclude
w  
Qiang Xue committed
169
	 * @return array the profiling results. Each array element has the following structure:
Qiang Xue committed
170
	 *  `array($token, $category, $time)`.
w  
Qiang Xue committed
171
	 */
w  
Qiang Xue committed
172
	public function getProfiling($categories = array(), $excludeCategories = array())
w  
Qiang Xue committed
173
	{
w  
Qiang Xue committed
174 175 176
		$timings = $this->calculateTimings();
		if (empty($categories) && empty($excludeCategories)) {
			return $timings;
w  
Qiang Xue committed
177
		}
w  
Qiang Xue committed
178 179 180 181 182

		foreach ($timings as $i => $timing) {
			$matched = empty($categories);
			foreach ($categories as $category) {
				$prefix = rtrim($category, '*');
Qiang Xue committed
183
				if (strpos($timing[1], $prefix) === 0 && ($timing[1] === $category || $prefix !== $category)) {
w  
Qiang Xue committed
184 185 186 187 188 189 190 191 192
					$matched = true;
					break;
				}
			}

			if ($matched) {
				foreach ($excludeCategories as $category) {
					$prefix = rtrim($category, '*');
					foreach ($timings as $i => $timing) {
Qiang Xue committed
193
						if (strpos($timing[1], $prefix) === 0 && ($timing[1] === $category || $prefix !== $category)) {
w  
Qiang Xue committed
194 195 196 197 198 199 200 201 202
							$matched = false;
							break;
						}
					}
				}
			}

			if (!$matched) {
				unset($timings[$i]);
w  
Qiang Xue committed
203 204
			}
		}
w  
Qiang Xue committed
205
		return array_values($timings);
w  
Qiang Xue committed
206 207 208 209
	}

	private function calculateTimings()
	{
w  
Qiang Xue committed
210
		$timings = array();
w  
Qiang Xue committed
211 212 213

		$stack = array();
		foreach ($this->messages as $log) {
Qiang Xue committed
214 215
			list($token, $level, $category, $timestamp) = $log;
			if ($level == self::LEVEL_PROFILE_BEGIN) {
w  
Qiang Xue committed
216
				$stack[] = $log;
Qiang Xue committed
217
			} elseif ($level == self::LEVEL_PROFILE_END) {
Qiang Xue committed
218 219 220
				if (($last = array_pop($stack)) !== null && $last[0] === $token) {
					$timings[] = array($token, $category, $timestamp - $last[3]);
				} else {
Qiang Xue committed
221
					throw new Exception("Unmatched profiling block: $token");
w  
Qiang Xue committed
222 223 224 225 226 227 228
				}
			}
		}

		$now = microtime(true);
		while (($last = array_pop($stack)) !== null) {
			$delta = $now - $last[3];
Qiang Xue committed
229
			$timings[] = array($last[0], $last[2], $delta);
w  
Qiang Xue committed
230 231
		}

w  
Qiang Xue committed
232
		return $timings;
w  
Qiang Xue committed
233 234 235
	}

}