Completed
Push — 2.1 ( 82d36d...04949e )
by Carsten
21s queued 11s
created

Logger   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 277
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 24%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 37
c 1
b 0
f 0
lcom 1
cbo 2
dl 0
loc 277
ccs 24
cts 100
cp 0.24
rs 8.6

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getElapsedTime() 0 4 1
A init() 0 11 1
C log() 0 23 8
A flush() 0 10 2
C getProfiling() 0 36 15
A getDbProfiling() 0 14 2
B calculateTimings() 0 28 6
A getLevelName() 0 13 2
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 Yii;
11
use yii\base\Component;
12
13
/**
14
 * Logger records logged messages in memory and sends them to different targets if [[dispatcher]] is set.
15
 *
16
 * A Logger instance can be accessed via `Yii::getLogger()`. You can call the method [[log()]] to record a single log message.
17
 * For convenience, a set of shortcut methods are provided for logging messages of various severity levels
18
 * via the [[Yii]] class:
19
 *
20
 * - [[Yii::trace()]]
21
 * - [[Yii::error()]]
22
 * - [[Yii::warning()]]
23
 * - [[Yii::info()]]
24
 * - [[Yii::beginProfile()]]
25
 * - [[Yii::endProfile()]]
26
 *
27
 * When the application ends or [[flushInterval]] is reached, Logger will call [[flush()]]
28
 * to send logged messages to different log targets, such as [[FileTarget|file]], [[EmailTarget|email]],
29
 * or [[DbTarget|database]], with the help of the [[dispatcher]].
30
 *
31
 * @property array $dbProfiling The first element indicates the number of SQL statements executed, and the
32
 * second element the total time spent in SQL execution. This property is read-only.
33
 * @property float $elapsedTime The total elapsed time in seconds for current request. This property is
34
 * read-only.
35
 * @property array $profiling The profiling results. Each element is an array consisting of these elements:
36
 * `info`, `category`, `timestamp`, `trace`, `level`, `duration`. This property is read-only.
37
 *
38
 * @author Qiang Xue <[email protected]>
39
 * @since 2.0
40
 */
41
class Logger extends Component
42
{
43
    /**
44
     * Error message level. An error message is one that indicates the abnormal termination of the
45
     * application and may require developer's handling.
46
     */
47
    const LEVEL_ERROR = 0x01;
48
    /**
49
     * Warning message level. A warning message is one that indicates some abnormal happens but
50
     * the application is able to continue to run. Developers should pay attention to this message.
51
     */
52
    const LEVEL_WARNING = 0x02;
53
    /**
54
     * Informational message level. An informational message is one that includes certain information
55
     * for developers to review.
56
     */
57
    const LEVEL_INFO = 0x04;
58
    /**
59
     * Tracing message level. An tracing message is one that reveals the code execution flow.
60
     */
61
    const LEVEL_TRACE = 0x08;
62
    /**
63
     * Profiling message level. This indicates the message is for profiling purpose.
64
     */
65
    const LEVEL_PROFILE = 0x40;
66
    /**
67
     * Profiling message level. This indicates the message is for profiling purpose. It marks the
68
     * beginning of a profiling block.
69
     */
70
    const LEVEL_PROFILE_BEGIN = 0x50;
71
    /**
72
     * Profiling message level. This indicates the message is for profiling purpose. It marks the
73
     * end of a profiling block.
74
     */
75
    const LEVEL_PROFILE_END = 0x60;
76
77
    /**
78
     * @var array logged messages. This property is managed by [[log()]] and [[flush()]].
79
     * Each log message is of the following structure:
80
     *
81
     * ```
82
     * [
83
     *   [0] => message (mixed, can be a string or some complex data, such as an exception object)
84
     *   [1] => level (integer)
85
     *   [2] => category (string)
86
     *   [3] => timestamp (float, obtained by microtime(true))
87
     *   [4] => traces (array, debug backtrace, contains the application code call stacks)
88
     * ]
89
     * ```
90
     */
91
    public $messages = [];
92
    /**
93
     * @var integer how many messages should be logged before they are flushed from memory and sent to targets.
94
     * Defaults to 1000, meaning the [[flush]] method will be invoked once every 1000 messages logged.
95
     * Set this property to be 0 if you don't want to flush messages until the application terminates.
96
     * This property mainly affects how much memory will be taken by the logged messages.
97
     * A smaller value means less memory, but will increase the execution time due to the overhead of [[flush()]].
98
     */
99
    public $flushInterval = 1000;
100
    /**
101
     * @var integer how much call stack information (file name and line number) should be logged for each message.
102
     * If it is greater than 0, at most that number of call stacks will be logged. Note that only application
103
     * call stacks are counted.
104
     */
105
    public $traceLevel = 0;
106
    /**
107
     * @var Dispatcher the message dispatcher
108
     */
109
    public $dispatcher;
110
111
112
    /**
113
     * Initializes the logger by registering [[flush()]] as a shutdown function.
114
     */
115 24
    public function init()
116
    {
117 24
        parent::init();
118 24
        register_shutdown_function(function () {
119
            // make regular flush before other shutdown functions, which allows session data collection and so on
120
            $this->flush();
121
            // make sure log entries written by shutdown functions are also flushed
122
            // ensure "flush()" is called last when there are multiple shutdown functions
123
            register_shutdown_function([$this, 'flush'], true);
124 24
        });
125 24
    }
126
127
    /**
128
     * Logs a message with the given type and category.
129
     * If [[traceLevel]] is greater than 0, additional call stack information about
130
     * the application code will be logged as well.
131
     * @param string|array $message the message to be logged. This can be a simple string or a more
132
     * complex data structure that will be handled by a [[Target|log target]].
133
     * @param integer $level the level of the message. This must be one of the following:
134
     * `Logger::LEVEL_ERROR`, `Logger::LEVEL_WARNING`, `Logger::LEVEL_INFO`, `Logger::LEVEL_TRACE`,
135
     * `Logger::LEVEL_PROFILE_BEGIN`, `Logger::LEVEL_PROFILE_END`.
136
     * @param string $category the category of the message.
137
     */
138 753
    public function log($message, $level, $category = 'application')
139
    {
140 753
        $time = microtime(true);
141 753
        $traces = [];
142 753
        if ($this->traceLevel > 0) {
143
            $count = 0;
144
            $ts = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
145
            array_pop($ts); // remove the last trace since it would be the entry script, not very useful
146
            foreach ($ts as $trace) {
147
                if (isset($trace['file'], $trace['line']) && strpos($trace['file'], YII2_PATH) !== 0) {
148
                    unset($trace['object'], $trace['args']);
149
                    $traces[] = $trace;
150
                    if (++$count >= $this->traceLevel) {
151
                        break;
152
                    }
153
                }
154
            }
155
        }
156 753
        $this->messages[] = [$message, $level, $category, $time, $traces];
157 753
        if ($this->flushInterval > 0 && count($this->messages) >= $this->flushInterval) {
158 45
            $this->flush();
159 45
        }
160 753
    }
161
162
    /**
163
     * Flushes log messages from memory to targets.
164
     * @param boolean $final whether this is a final call during a request.
165
     */
166 48
    public function flush($final = false)
167
    {
168 48
        $messages = $this->messages;
169
        // https://github.com/yiisoft/yii2/issues/5619
170
        // new messages could be logged while the existing ones are being handled by targets
171 48
        $this->messages = [];
172 48
        if ($this->dispatcher instanceof Dispatcher) {
173 34
            $this->dispatcher->dispatch($messages, $final);
174 34
        }
175 48
    }
176
177
    /**
178
     * Returns the total elapsed time since the start of the current request.
179
     * This method calculates the difference between now and the timestamp
180
     * defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning
181
     * of [[\yii\BaseYii]] class file.
182
     * @return float the total elapsed time in seconds for current request.
183
     */
184
    public function getElapsedTime()
185
    {
186
        return microtime(true) - YII_BEGIN_TIME;
187
    }
188
189
    /**
190
     * Returns the profiling results.
191
     *
192
     * By default, all profiling results will be returned. You may provide
193
     * `$categories` and `$excludeCategories` as parameters to retrieve the
194
     * results that you are interested in.
195
     *
196
     * @param array $categories list of categories that you are interested in.
197
     * You can use an asterisk at the end of a category to do a prefix match.
198
     * For example, 'yii\db\*' will match categories starting with 'yii\db\',
199
     * such as `yii\db\Connection`.
200
     * @param array $excludeCategories list of categories that you want to exclude
201
     * @return array the profiling results. Each element is an array consisting of these elements:
202
     * `info`, `category`, `timestamp`, `trace`, `level`, `duration`.
203
     */
204
    public function getProfiling($categories = [], $excludeCategories = [])
205
    {
206
        $timings = $this->calculateTimings($this->messages);
207
        if (empty($categories) && empty($excludeCategories)) {
208
            return $timings;
209
        }
210
211
        foreach ($timings as $i => $timing) {
212
            $matched = empty($categories);
213
            foreach ($categories as $category) {
214
                $prefix = rtrim($category, '*');
215
                if (($timing['category'] === $category || $prefix !== $category) && strpos($timing['category'], $prefix) === 0) {
216
                    $matched = true;
217
                    break;
218
                }
219
            }
220
221
            if ($matched) {
222
                foreach ($excludeCategories as $category) {
223
                    $prefix = rtrim($category, '*');
224
                    foreach ($timings as $i => $timing) {
225
                        if (($timing['category'] === $category || $prefix !== $category) && strpos($timing['category'], $prefix) === 0) {
226
                            $matched = false;
227
                            break;
228
                        }
229
                    }
230
                }
231
            }
232
233
            if (!$matched) {
234
                unset($timings[$i]);
235
            }
236
        }
237
238
        return array_values($timings);
239
    }
240
241
    /**
242
     * Returns the statistical results of DB queries.
243
     * The results returned include the number of SQL statements executed and
244
     * the total time spent.
245
     * @return array the first element indicates the number of SQL statements executed,
246
     * and the second element the total time spent in SQL execution.
247
     */
248
    public function getDbProfiling()
249
    {
250
        $timings = $this->getProfiling([
251
            'yii\db\Command::query',
252
            'yii\db\Command::execute',
253
        ]);
254
        $count = count($timings);
255
        $time = 0;
256
        foreach ($timings as $timing) {
257
            $time += $timing['duration'];
258
        }
259
260
        return [$count, $time];
261
    }
262
263
    /**
264
     * Calculates the elapsed time for the given log messages.
265
     * @param array $messages the log messages obtained from profiling
266
     * @return array timings. Each element is an array consisting of these elements:
267
     * `info`, `category`, `timestamp`, `trace`, `level`, `duration`.
268
     */
269
    public function calculateTimings($messages)
270
    {
271
        $timings = [];
272
        $stack = [];
273
274
        foreach ($messages as $i => $log) {
275
            list($token, $level, $category, $timestamp, $traces) = $log;
0 ignored issues
show
Unused Code introduced by
The assignment to $category is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $traces is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
276
            $log[5] = $i;
277
            if ($level == self::LEVEL_PROFILE_BEGIN) {
278
                $stack[] = $log;
279
            } elseif ($level == self::LEVEL_PROFILE_END) {
280
                if (($last = array_pop($stack)) !== null && $last[0] === $token) {
281
                    $timings[$last[5]] = [
282
                        'info' => $last[0],
283
                        'category' => $last[2],
284
                        'timestamp' => $last[3],
285
                        'trace' => $last[4],
286
                        'level' => count($stack),
287
                        'duration' => $timestamp - $last[3],
288
                    ];
289
                }
290
            }
291
        }
292
293
        ksort($timings);
294
295
        return array_values($timings);
296
    }
297
298
299
    /**
300
     * Returns the text display of the specified level.
301
     * @param integer $level the message level, e.g. [[LEVEL_ERROR]], [[LEVEL_WARNING]].
302
     * @return string the text display of the level
303
     */
304 2
    public static function getLevelName($level)
305
    {
306
        static $levels = [
307
            self::LEVEL_ERROR => 'error',
308
            self::LEVEL_WARNING => 'warning',
309
            self::LEVEL_INFO => 'info',
310
            self::LEVEL_TRACE => 'trace',
311
            self::LEVEL_PROFILE_BEGIN => 'profile begin',
312
            self::LEVEL_PROFILE_END => 'profile end',
313 2
        ];
314
315 2
        return isset($levels[$level]) ? $levels[$level] : 'unknown';
316
    }
317
}
318