/
CLogger.php
284 lines (270 loc) · 9.07 KB
/
CLogger.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
<?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.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id: CLogger.php 3206 2011-05-09 09:26:24Z qiang.xue $
* @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)
* @since 1.0.6
*/
private $_timings;
/**
* 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->flush($this->autoDump);
}
/**
* 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.
* @since 1.0.6
*/
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
* @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);
}
}