1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @link http://www.yiiframework.com/ |
4
|
|
|
* @copyright Copyright (c) 2008 Yii Software LLC |
5
|
|
|
* @license http://www.yiiframework.com/license/ |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace yii\log; |
9
|
|
|
|
10
|
|
|
use Psr\Log\InvalidArgumentException; |
11
|
|
|
use Psr\Log\LoggerInterface; |
12
|
|
|
use Psr\Log\LogLevel; |
13
|
|
|
use Psr\Log\LoggerTrait; |
14
|
|
|
use Yii; |
15
|
|
|
use yii\base\Component; |
16
|
|
|
use yii\base\ErrorHandler; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Logger records logged messages in memory and sends them to different targets according to [[targets]]. |
20
|
|
|
* |
21
|
|
|
* A Logger instance can be accessed via `Yii::getLogger()`. You can call the method [[log()]] to record a single log message. |
22
|
|
|
* For convenience, a set of shortcut methods are provided for logging messages of various severity levels |
23
|
|
|
* via the [[Yii]] class: |
24
|
|
|
* |
25
|
|
|
* - [[Yii::debug()]] |
26
|
|
|
* - [[Yii::error()]] |
27
|
|
|
* - [[Yii::warning()]] |
28
|
|
|
* - [[Yii::info()]] |
29
|
|
|
* |
30
|
|
|
* For more details and usage information on Logger, see the [guide article on logging](guide:runtime-logging) |
31
|
|
|
* and [PSR-3 specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). |
32
|
|
|
* |
33
|
|
|
* When the application ends or [[flushInterval]] is reached, Logger will call [[flush()]] |
34
|
|
|
* to send logged messages to different log targets, such as [[FileTarget|file]], [[EmailTarget|email]], |
35
|
|
|
* or [[DbTarget|database]], according to the [[targets]]. |
36
|
|
|
* |
37
|
|
|
* @property array|Target[] $targets the log targets. See [[setTargets()]] for details. |
38
|
|
|
* @property float $elapsedTime The total elapsed time in seconds for current request. This property is |
39
|
|
|
* read-only. |
40
|
|
|
* |
41
|
|
|
* @author Qiang Xue <[email protected]> |
42
|
|
|
* @since 2.0 |
43
|
|
|
*/ |
44
|
|
|
class Logger extends Component implements LoggerInterface |
45
|
|
|
{ |
46
|
|
|
use LoggerTrait; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var array logged messages. This property is managed by [[log()]] and [[flush()]]. |
50
|
|
|
* Each log message is of the following structure: |
51
|
|
|
* |
52
|
|
|
* ``` |
53
|
|
|
* [ |
54
|
|
|
* [0] => level (string) |
55
|
|
|
* [1] => message (mixed, can be a string or some complex data, such as an exception object) |
56
|
|
|
* [2] => context (array) |
57
|
|
|
* ] |
58
|
|
|
* ``` |
59
|
|
|
* |
60
|
|
|
* Message context has a following keys: |
61
|
|
|
* |
62
|
|
|
* - category: string, message category. |
63
|
|
|
* - time: float, message timestamp obtained by microtime(true). |
64
|
|
|
* - trace: array, debug backtrace, contains the application code call stacks. |
65
|
|
|
* - memory: int, memory usage in bytes, obtained by `memory_get_usage()`, available since version 2.0.11. |
66
|
|
|
*/ |
67
|
|
|
public $messages = []; |
68
|
|
|
/** |
69
|
|
|
* @var int how many messages should be logged before they are flushed from memory and sent to targets. |
70
|
|
|
* Defaults to 1000, meaning the [[flush]] method will be invoked once every 1000 messages logged. |
71
|
|
|
* Set this property to be 0 if you don't want to flush messages until the application terminates. |
72
|
|
|
* This property mainly affects how much memory will be taken by the logged messages. |
73
|
|
|
* A smaller value means less memory, but will increase the execution time due to the overhead of [[flush()]]. |
74
|
|
|
*/ |
75
|
|
|
public $flushInterval = 1000; |
76
|
|
|
/** |
77
|
|
|
* @var int how much call stack information (file name and line number) should be logged for each message. |
78
|
|
|
* If it is greater than 0, at most that number of call stacks will be logged. Note that only application |
79
|
|
|
* call stacks are counted. |
80
|
|
|
*/ |
81
|
|
|
public $traceLevel = 0; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* @var array|Target[] the log targets. Each array element represents a single [[Target|log target]] instance |
85
|
|
|
* or the configuration for creating the log target instance. |
86
|
|
|
* @since 2.1 |
87
|
|
|
*/ |
88
|
|
|
private $_targets = []; |
89
|
|
|
/** |
90
|
|
|
* @var bool whether [[targets]] have been initialized, e.g. ensured to be objects. |
91
|
|
|
* @since 2.1 |
92
|
|
|
*/ |
93
|
|
|
private $_isTargetsInitialized = false; |
94
|
|
|
|
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* @return Target[] the log targets. Each array element represents a single [[Target|log target]] instance. |
98
|
|
|
* @since 2.1 |
99
|
|
|
*/ |
100
|
35 |
|
public function getTargets() |
101
|
|
|
{ |
102
|
35 |
|
if (!$this->_isTargetsInitialized) { |
103
|
27 |
|
foreach ($this->_targets as $name => $target) { |
104
|
27 |
|
if (!$target instanceof Target) { |
105
|
27 |
|
$this->_targets[$name] = Yii::createObject($target); |
106
|
|
|
} |
107
|
|
|
} |
108
|
27 |
|
$this->_isTargetsInitialized = true; |
109
|
|
|
} |
110
|
35 |
|
return $this->_targets; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* @param array|Target[] $targets the log targets. Each array element represents a single [[Target|log target]] instance |
115
|
|
|
* or the configuration for creating the log target instance. |
116
|
|
|
* @since 2.1 |
117
|
|
|
*/ |
118
|
27 |
|
public function setTargets($targets) |
119
|
|
|
{ |
120
|
27 |
|
$this->_targets = $targets; |
121
|
27 |
|
$this->_isTargetsInitialized = false; |
122
|
27 |
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Adds extra target to [[targets]]. |
126
|
|
|
* @param Target|array $target the log target instance or its DI compatible configuration. |
127
|
|
|
* @param string|null $name array key to be used to store target, if `null` is given target will be append |
128
|
|
|
* to the end of the array by natural integer key. |
129
|
|
|
*/ |
130
|
1 |
|
public function addTarget($target, $name = null) |
131
|
|
|
{ |
132
|
1 |
|
if (!$target instanceof Target) { |
133
|
|
|
$this->_isTargetsInitialized = false; |
134
|
|
|
} |
135
|
1 |
|
if ($name === null) { |
136
|
1 |
|
$this->_targets[] = $target; |
137
|
|
|
} else { |
138
|
1 |
|
$this->_targets[$name] = $target; |
139
|
|
|
} |
140
|
1 |
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Initializes the logger by registering [[flush()]] as a shutdown function. |
144
|
|
|
*/ |
145
|
21 |
|
public function init() |
146
|
|
|
{ |
147
|
21 |
|
parent::init(); |
148
|
21 |
|
register_shutdown_function(function () { |
149
|
|
|
// make regular flush before other shutdown functions, which allows session data collection and so on |
150
|
|
|
$this->flush(); |
151
|
|
|
// make sure log entries written by shutdown functions are also flushed |
152
|
|
|
// ensure "flush()" is called last when there are multiple shutdown functions |
153
|
|
|
register_shutdown_function([$this, 'flush'], true); |
154
|
21 |
|
}); |
155
|
21 |
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* {@inheritdoc} |
159
|
|
|
*/ |
160
|
1536 |
|
public function log($level, $message, array $context = array()) |
161
|
|
|
{ |
162
|
1536 |
|
if (!is_string($message)) { |
163
|
|
|
if (is_scalar($message)) { |
164
|
|
|
$message = (string)$message; |
165
|
|
|
} elseif (is_object($message)) { |
166
|
|
|
if ($message instanceof \Throwable) { |
167
|
|
|
if (!isset($context['exception'])) { |
168
|
|
|
$context['exception'] = $message; |
169
|
|
|
} |
170
|
|
|
$message = $message->__toString(); |
171
|
|
|
} elseif (method_exists($message, '__toString')) { |
172
|
|
|
$message = $message->__toString(); |
173
|
|
|
} else { |
174
|
|
|
throw new InvalidArgumentException('The log message MUST be a string or object implementing __toString()'); |
175
|
|
|
} |
176
|
|
|
} else { |
177
|
|
|
throw new InvalidArgumentException('The log message MUST be a string or object implementing __toString()'); |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
|
181
|
1536 |
|
if (!isset($context['time'])) { |
182
|
1536 |
|
$context['time'] = microtime(true); |
183
|
|
|
} |
184
|
1536 |
|
if (!isset($context['trace'])) { |
185
|
1536 |
|
$traces = []; |
186
|
1536 |
|
if ($this->traceLevel > 0) { |
187
|
1 |
|
$count = 0; |
188
|
1 |
|
$ts = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); |
189
|
1 |
|
array_pop($ts); // remove the last trace since it would be the entry script, not very useful |
190
|
1 |
|
foreach ($ts as $trace) { |
191
|
1 |
|
if (isset($trace['file'], $trace['line']) && strpos($trace['file'], YII2_PATH) !== 0) { |
192
|
1 |
|
unset($trace['object'], $trace['args']); |
193
|
1 |
|
$traces[] = $trace; |
194
|
1 |
|
if (++$count >= $this->traceLevel) { |
195
|
1 |
|
break; |
196
|
|
|
} |
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
} |
200
|
1536 |
|
$context['trace'] = $traces; |
201
|
|
|
} |
202
|
|
|
|
203
|
1536 |
|
if (!isset($context['memory'])) { |
204
|
1536 |
|
$context['memory'] = memory_get_usage(); |
205
|
|
|
} |
206
|
|
|
|
207
|
1536 |
|
if (!isset($context['category'])) { |
208
|
23 |
|
$context['category'] = 'application'; |
209
|
|
|
} |
210
|
|
|
|
211
|
1536 |
|
$message = $this->parseMessage($message, $context); |
212
|
|
|
|
213
|
1536 |
|
$this->messages[] = [$level, $message, $context]; |
214
|
|
|
|
215
|
1536 |
|
if ($this->flushInterval > 0 && count($this->messages) >= $this->flushInterval) { |
216
|
28 |
|
$this->flush(); |
217
|
|
|
} |
218
|
1536 |
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Flushes log messages from memory to targets. |
222
|
|
|
* @param bool $final whether this is a final call during a request. |
223
|
|
|
*/ |
224
|
36 |
|
public function flush($final = false) |
225
|
|
|
{ |
226
|
36 |
|
$messages = $this->messages; |
227
|
|
|
// https://github.com/yiisoft/yii2/issues/5619 |
228
|
|
|
// new messages could be logged while the existing ones are being handled by targets |
229
|
36 |
|
$this->messages = []; |
230
|
|
|
|
231
|
36 |
|
$this->dispatch($messages, $final); |
232
|
36 |
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Dispatches the logged messages to [[targets]]. |
236
|
|
|
* @param array $messages the logged messages |
237
|
|
|
* @param bool $final whether this method is called at the end of the current application |
238
|
|
|
* @since 2.1 |
239
|
|
|
*/ |
240
|
37 |
|
protected function dispatch($messages, $final) |
241
|
|
|
{ |
242
|
37 |
|
$targetErrors = []; |
243
|
37 |
|
foreach ($this->targets as $target) { |
244
|
34 |
|
if ($target->enabled) { |
245
|
|
|
try { |
246
|
33 |
|
$target->collect($messages, $final); |
247
|
1 |
|
} catch (\Exception $e) { |
248
|
1 |
|
$target->enabled = false; |
249
|
1 |
|
$targetErrors[] = [ |
250
|
34 |
|
'Unable to send log via ' . get_class($target) . ': ' . ErrorHandler::convertExceptionToString($e), |
251
|
|
|
LogLevel::WARNING, |
252
|
|
|
__METHOD__, |
253
|
1 |
|
microtime(true), |
254
|
|
|
[], |
255
|
|
|
]; |
256
|
|
|
} |
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
|
260
|
37 |
|
if (!empty($targetErrors)) { |
261
|
1 |
|
$this->dispatch($targetErrors, true); |
262
|
|
|
} |
263
|
37 |
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Parses log message resolving placeholders in the form: '{foo}', where foo |
267
|
|
|
* will be replaced by the context data in key "foo". |
268
|
|
|
* @param string $message log message. |
269
|
|
|
* @param array $context message context. |
270
|
|
|
* @return string parsed message. |
271
|
|
|
* @since 2.1 |
272
|
|
|
*/ |
273
|
|
|
protected function parseMessage($message, array $context) |
274
|
|
|
{ |
275
|
1536 |
|
return preg_replace_callback('/\\{([\\w\\.]+)\\}/is', function ($matches) use ($context) { |
276
|
8 |
|
$placeholderName = $matches[1]; |
277
|
8 |
|
if (isset($context[$placeholderName])) { |
278
|
1 |
|
return (string)$context[$placeholderName]; |
279
|
|
|
} |
280
|
7 |
|
return $matches[0]; |
281
|
1536 |
|
}, $message); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* Returns the total elapsed time since the start of the current request. |
286
|
|
|
* This method calculates the difference between now and the timestamp |
287
|
|
|
* defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning |
288
|
|
|
* of [[\yii\BaseYii]] class file. |
289
|
|
|
* @return float the total elapsed time in seconds for current request. |
290
|
|
|
*/ |
291
|
1 |
|
public function getElapsedTime() |
292
|
|
|
{ |
293
|
1 |
|
return microtime(true) - YII_BEGIN_TIME; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Returns the text display of the specified level. |
298
|
|
|
* @param mixed $level the message level, e.g. [[LogLevel::ERROR]], [[LogLevel::WARNING]]. |
299
|
|
|
* @return string the text display of the level |
300
|
|
|
*/ |
301
|
3 |
|
public static function getLevelName($level) |
302
|
|
|
{ |
303
|
3 |
|
if (is_string($level)) { |
304
|
3 |
|
return $level; |
305
|
|
|
} |
306
|
1 |
|
return 'unknown'; |
307
|
|
|
} |
308
|
|
|
} |
309
|
|
|
|