Completed
Push — update-to-php-71 ( f475af )
by Alexander
15:41 queued 04:41
created

Target::filterMessages()   C

Complexity

Conditions 15
Paths 26

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 15

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 19
cts 19
cp 1
rs 5.0504
c 0
b 0
f 0
cc 15
eloc 19
nc 26
nop 4
crap 15

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
use yii\base\InvalidConfigException;
13
use yii\helpers\ArrayHelper;
14
use yii\helpers\VarDumper;
15
use yii\web\Request;
16
17
/**
18
 * Target is the base class for all log target classes.
19
 *
20
 * A log target object will filter the messages logged by [[Logger]] according
21
 * to its [[levels]] and [[categories]] properties. It may also export the filtered
22
 * messages to specific destination defined by the target, such as emails, files.
23
 *
24
 * Level filter and category filter are combinatorial, i.e., only messages
25
 * satisfying both filter conditions will be handled. Additionally, you
26
 * may specify [[except]] to exclude messages of certain categories.
27
 *
28
 * @property int $levels The message levels that this target is interested in. This is a bitmap of level
29
 * values. Defaults to 0, meaning  all available levels. Note that the type of this property differs in getter
30
 * and setter. See [[getLevels()]] and [[setLevels()]] for details.
31
 *
32
 * For more details and usage information on Target, see the [guide article on logging & targets](guide:runtime-logging).
33
 *
34
 * @author Qiang Xue <[email protected]>
35
 * @since 2.0
36
 */
37
abstract class Target extends Component
38
{
39
    /**
40
     * @var bool whether to enable this log target. Defaults to true.
41
     */
42
    public $enabled = true;
43
    /**
44
     * @var array list of message categories that this target is interested in. Defaults to empty, meaning all categories.
45
     * You can use an asterisk at the end of a category so that the category may be used to
46
     * match those categories sharing the same common prefix. For example, 'yii\db\*' will match
47
     * categories starting with 'yii\db\', such as `yii\db\Connection`.
48
     */
49
    public $categories = [];
50
    /**
51
     * @var array list of message categories that this target is NOT interested in. Defaults to empty, meaning no uninteresting messages.
52
     * If this property is not empty, then any category listed here will be excluded from [[categories]].
53
     * You can use an asterisk at the end of a category so that the category can be used to
54
     * match those categories sharing the same common prefix. For example, 'yii\db\*' will match
55
     * categories starting with 'yii\db\', such as `yii\db\Connection`.
56
     * @see categories
57
     */
58
    public $except = [];
59
    /**
60
     * @var array list of the PHP predefined variables that should be logged in a message.
61
     * Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be logged.
62
     *
63
     * Defaults to `['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER']`.
64
     *
65
     * Since version 2.0.9 additional syntax can be used:
66
     * Each element could be specified as one of the following:
67
     *
68
     * - `var` - `var` will be logged.
69
     * - `var.key` - only `var[key]` key will be logged.
70
     * - `!var.key` - `var[key]` key will be excluded.
71
     *
72
     * @see \yii\helpers\ArrayHelper::filter()
73
     */
74
    public $logVars = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'];
75
    /**
76
     * @var callable a PHP callable that returns a string to be prefixed to every exported message.
77
     *
78
     * If not set, [[getMessagePrefix()]] will be used, which prefixes the message with context information
79
     * such as user IP, user ID and session ID.
80
     *
81
     * The signature of the callable should be `function ($message)`.
82
     */
83
    public $prefix;
84
    /**
85
     * @var int how many messages should be accumulated before they are exported.
86
     * Defaults to 1000. Note that messages will always be exported when the application terminates.
87
     * Set this property to be 0 if you don't want to export messages until the application terminates.
88
     */
89
    public $exportInterval = 1000;
90
    /**
91
     * @var array the messages that are retrieved from the logger so far by this log target.
92
     * Please refer to [[Logger::messages]] for the details about the message structure.
93
     */
94
    public $messages = [];
95
96
    private $_levels = 0;
97
98
99
    /**
100
     * Exports log [[messages]] to a specific destination.
101
     * Child classes must implement this method.
102
     */
103
    abstract public function export();
104
105
    /**
106
     * Processes the given log messages.
107
     * This method will filter the given messages with [[levels]] and [[categories]].
108
     * And if requested, it will also export the filtering result to specific medium (e.g. email).
109
     * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure
110
     * of each message.
111
     * @param bool $final whether this method is called at the end of the current application
112
     */
113 41
    public function collect($messages, $final)
114
    {
115 41
        $this->messages = array_merge($this->messages, static::filterMessages($messages, $this->getLevels(), $this->categories, $this->except));
116 41
        $count = count($this->messages);
117 41
        if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) {
118 25
            if (($context = $this->getContextMessage()) !== '') {
119 6
                $this->messages[] = [$context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME];
120
            }
121
            // set exportInterval to 0 to avoid triggering export again while exporting
122 25
            $oldExportInterval = $this->exportInterval;
123 25
            $this->exportInterval = 0;
124 25
            $this->export();
125 25
            $this->exportInterval = $oldExportInterval;
126
127 25
            $this->messages = [];
128
        }
129 41
    }
130
131
    /**
132
     * Generates the context information to be logged.
133
     * The default implementation will dump user information, system variables, etc.
134
     * @return string the context information. If an empty string, it means no context information.
135
     */
136 26
    protected function getContextMessage()
137
    {
138 26
        $context = ArrayHelper::filter($GLOBALS, $this->logVars);
139 26
        $result = [];
140 26
        foreach ($context as $key => $value) {
141 7
            $result[] = "\${$key} = " . VarDumper::dumpAsString($value);
142
        }
143 26
        return implode("\n\n", $result);
144
    }
145
146
    /**
147
     * @return int the message levels that this target is interested in. This is a bitmap of
148
     * level values. Defaults to 0, meaning  all available levels.
149
     */
150 43
    public function getLevels()
151
    {
152 43
        return $this->_levels;
153
    }
154
155
    /**
156
     * Sets the message levels that this target is interested in.
157
     *
158
     * The parameter can be either an array of interested level names or an integer representing
159
     * the bitmap of the interested level values. Valid level names include: 'error',
160
     * 'warning', 'info', 'trace' and 'profile'; valid level values include:
161
     * [[Logger::LEVEL_ERROR]], [[Logger::LEVEL_WARNING]], [[Logger::LEVEL_INFO]],
162
     * [[Logger::LEVEL_TRACE]] and [[Logger::LEVEL_PROFILE]].
163
     *
164
     * For example,
165
     *
166
     * ```php
167
     * ['error', 'warning']
168
     * // which is equivalent to:
169
     * Logger::LEVEL_ERROR | Logger::LEVEL_WARNING
170
     * ```
171
     *
172
     * @param array|int $levels message levels that this target is interested in.
173
     * @throws InvalidConfigException if $levels value is not correct.
174
     */
175 19
    public function setLevels($levels)
176
    {
177 19
        static $levelMap = [
178
            'error' => Logger::LEVEL_ERROR,
179
            'warning' => Logger::LEVEL_WARNING,
180
            'info' => Logger::LEVEL_INFO,
181
            'trace' => Logger::LEVEL_TRACE,
182
            'profile' => Logger::LEVEL_PROFILE,
183
        ];
184 19
        if (is_array($levels)) {
185 11
            $this->_levels = 0;
186 11
            foreach ($levels as $level) {
187 11
                if (isset($levelMap[$level])) {
188 11
                    $this->_levels |= $levelMap[$level];
189
                } else {
190 11
                    throw new InvalidConfigException("Unrecognized level: $level");
191
                }
192
            }
193
        } else {
194 8
            $bitmapValues = array_reduce($levelMap, function ($carry, $item) {
195 8
                return $carry | $item;
196 8
            });
197 8
            if (!($bitmapValues & $levels) && $levels !== 0) {
198 1
                throw new InvalidConfigException("Incorrect $levels value");
199
            }
200 8
            $this->_levels = $levels;
201
        }
202 19
    }
203
204
    /**
205
     * Filters the given messages according to their categories and levels.
206
     * @param array $messages messages to be filtered.
207
     * The message structure follows that in [[Logger::messages]].
208
     * @param int $levels the message levels to filter by. This is a bitmap of
209
     * level values. Value 0 means allowing all levels.
210
     * @param array $categories the message categories to filter by. If empty, it means all categories are allowed.
211
     * @param array $except the message categories to exclude. If empty, it means all categories are allowed.
212
     * @return array the filtered messages.
213
     */
214 41
    public static function filterMessages($messages, $levels = 0, $categories = [], $except = [])
215
    {
216 41
        foreach ($messages as $i => $message) {
217 41
            if ($levels && !($levels & $message[1])) {
218 28
                unset($messages[$i]);
219 28
                continue;
220
            }
221
222 26
            $matched = empty($categories);
223 26
            foreach ($categories as $category) {
224 11
                if ($message[2] === $category || !empty($category) && substr_compare($category, '*', -1, 1) === 0 && strpos($message[2], rtrim($category, '*')) === 0) {
225 10
                    $matched = true;
226 10
                    break;
227
                }
228
            }
229
230 26
            if ($matched) {
231 25
                foreach ($except as $category) {
232 1
                    $prefix = rtrim($category, '*');
233 1
                    if (($message[2] === $category || $prefix !== $category) && strpos($message[2], $prefix) === 0) {
234 1
                        $matched = false;
235 1
                        break;
236
                    }
237
                }
238
            }
239
240 26
            if (!$matched) {
241 10
                unset($messages[$i]);
242
            }
243
        }
244 41
        return $messages;
245
    }
246
247
    /**
248
     * Formats a log message for display as a string.
249
     * @param array $message the log message to be formatted.
250
     * The message structure follows that in [[Logger::messages]].
251
     * @return string the formatted message
252
     */
253 2
    public function formatMessage($message)
254
    {
255 2
        list($text, $level, $category, $timestamp) = $message;
256 2
        $level = Logger::getLevelName($level);
257 2
        if (!is_string($text)) {
258
            // exceptions may not be serializable if in the call stack somewhere is a Closure
259
            if ($text instanceof \Throwable) {
260
                $text = (string) $text;
261
            } else {
262
                $text = VarDumper::export($text);
263
            }
264
        }
265 2
        $traces = [];
266 2
        if (isset($message[4])) {
267 2
            foreach ($message[4] as $trace) {
268
                $traces[] = "in {$trace['file']}:{$trace['line']}";
269
            }
270
        }
271
272 2
        $prefix = $this->getMessagePrefix($message);
273 2
        return date('Y-m-d H:i:s', $timestamp) . " {$prefix}[$level][$category] $text"
274 2
            . (empty($traces) ? '' : "\n    " . implode("\n    ", $traces));
275
    }
276
277
    /**
278
     * Returns a string to be prefixed to the given message.
279
     * If [[prefix]] is configured it will return the result of the callback.
280
     * The default implementation will return user IP, user ID and session ID as a prefix.
281
     * @param array $message the message being exported.
282
     * The message structure follows that in [[Logger::messages]].
283
     * @return string the prefix string
284
     */
285 8
    public function getMessagePrefix($message)
286
    {
287 8
        if ($this->prefix !== null) {
288
            return call_user_func($this->prefix, $message);
289
        }
290
291 8
        if (Yii::$app === null) {
292
            return '';
293
        }
294
295 8
        $request = Yii::$app->getRequest();
296 8
        $ip = $request instanceof Request ? $request->getUserIP() : '-';
297
298
        /* @var $user \yii\web\User */
299 8
        $user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
300 8
        if ($user && ($identity = $user->getIdentity(false))) {
301
            $userID = $identity->getId();
0 ignored issues
show
Bug introduced by
The method getId cannot be called on $identity (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
302
        } else {
303 8
            $userID = '-';
304
        }
305
306
        /* @var $session \yii\web\Session */
307 8
        $session = Yii::$app->has('session', true) ? Yii::$app->get('session') : null;
308 8
        $sessionID = $session && $session->getIsActive() ? $session->getId() : '-';
309
310 8
        return "[$ip][$userID][$sessionID]";
311
    }
312
}
313