Completed
Push — master ( 353cc7...e4eacc )
by Dmitry
13:48
created

Target::setEnabled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
374 1
    }
375
376
    /**
377
     * Check whether the log target is enabled.
378
     * @property bool Indicates whether this log target is enabled. Defaults to true.
379
     * @return bool A value indicating whether this log target is enabled.
380
     */
381 379
    public function getEnabled()
382
    {
383 379
        if (is_callable($this->_enabled)) {
384 1
            return call_user_func($this->_enabled, $this);
385
        }
386
387 379
        return $this->_enabled;
388
    }
389
390
    /**
391
     * Returns formatted ('Y-m-d H:i:s') timestamp for message.
392
     * If [[microtime]] is configured to true it will return format 'Y-m-d H:i:s.u'.
393
     * @param float $timestamp
394
     * @return string
395
     * @since 2.0.13
396
     */
397 3
    protected function getTime($timestamp)
398
    {
399 3
        $parts = explode('.', sprintf('%F', $timestamp));
400
401 3
        return date('Y-m-d H:i:s', $parts[0]) . ($this->microtime ? ('.' . $parts[1]) : '');
402
    }
403
}
404