Completed
Push — master ( 324884...59e99a )
by Alexander
41:49 queued 38:19
created

Target::getEnabled()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
ccs 4
cts 4
cp 1
cc 2
nc 2
nop 0
crap 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
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 = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'];
77
    /**
78
     * @var callable a PHP callable that returns a string to be prefixed to every exported message.
79
     *
80
     * If not set, [[getMessagePrefix()]] will be used, which prefixes the message with context information
81
     * such as user IP, user ID and session ID.
82
     *
83
     * The signature of the callable should be `function ($message)`.
84
     */
85
    public $prefix;
86
    /**
87
     * @var int how many messages should be accumulated before they are exported.
88
     * Defaults to 1000. Note that messages will always be exported when the application terminates.
89
     * Set this property to be 0 if you don't want to export messages until the application terminates.
90
     */
91
    public $exportInterval = 1000;
92
    /**
93
     * @var array the messages that are retrieved from the logger so far by this log target.
94
     * Please refer to [[Logger::messages]] for the details about the message structure.
95
     */
96
    public $messages = [];
97
    /**
98
     * @var bool whether to log time with microseconds.
99
     * Defaults to false.
100
     * @since 2.0.13
101
     */
102
    public $microtime = false;
103
104
    private $_levels = 0;
105
    private $_enabled = true;
106
107
108
    /**
109
     * Exports log [[messages]] to a specific destination.
110
     * Child classes must implement this method.
111
     */
112
    abstract public function export();
113
114
    /**
115
     * Processes the given log messages.
116
     * This method will filter the given messages with [[levels]] and [[categories]].
117
     * And if requested, it will also export the filtering result to specific medium (e.g. email).
118
     * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure
119
     * of each message.
120
     * @param bool $final whether this method is called at the end of the current application
121
     */
122 378
    public function collect($messages, $final)
123
    {
124 378
        $this->messages = array_merge($this->messages, static::filterMessages($messages, $this->getLevels(), $this->categories, $this->except));
125 378
        $count = count($this->messages);
126 378
        if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) {
127 27
            if (($context = $this->getContextMessage()) !== '') {
128 6
                $this->messages[] = [$context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME];
129
            }
130
            // set exportInterval to 0 to avoid triggering export again while exporting
131 27
            $oldExportInterval = $this->exportInterval;
132 27
            $this->exportInterval = 0;
133 27
            $this->export();
134 27
            $this->exportInterval = $oldExportInterval;
135
136 27
            $this->messages = [];
137
        }
138 378
    }
139
140
    /**
141
     * Generates the context information to be logged.
142
     * The default implementation will dump user information, system variables, etc.
143
     * @return string the context information. If an empty string, it means no context information.
144
     */
145 28
    protected function getContextMessage()
146
    {
147 28
        $context = ArrayHelper::filter($GLOBALS, $this->logVars);
148 28
        $result = [];
149 28
        foreach ($context as $key => $value) {
150 7
            $result[] = "\${$key} = " . VarDumper::dumpAsString($value);
151
        }
152
153 28
        return implode("\n\n", $result);
154
    }
155
156
    /**
157
     * @return int the message levels that this target is interested in. This is a bitmap of
158
     * level values. Defaults to 0, meaning  all available levels.
159
     */
160 380
    public function getLevels()
161
    {
162 380
        return $this->_levels;
163
    }
164
165
    /**
166
     * Sets the message levels that this target is interested in.
167
     *
168
     * The parameter can be either an array of interested level names or an integer representing
169
     * the bitmap of the interested level values. Valid level names include: 'error',
170
     * 'warning', 'info', 'trace' and 'profile'; valid level values include:
171
     * [[Logger::LEVEL_ERROR]], [[Logger::LEVEL_WARNING]], [[Logger::LEVEL_INFO]],
172
     * [[Logger::LEVEL_TRACE]] and [[Logger::LEVEL_PROFILE]].
173
     *
174
     * For example,
175
     *
176
     * ```php
177
     * ['error', 'warning']
178
     * // which is equivalent to:
179
     * Logger::LEVEL_ERROR | Logger::LEVEL_WARNING
180
     * ```
181
     *
182
     * @param array|int $levels message levels that this target is interested in.
183
     * @throws InvalidConfigException if $levels value is not correct.
184
     */
185 24
    public function setLevels($levels)
186
    {
187 24
        static $levelMap = [
188
            'error' => Logger::LEVEL_ERROR,
189
            'warning' => Logger::LEVEL_WARNING,
190
            'info' => Logger::LEVEL_INFO,
191
            'trace' => Logger::LEVEL_TRACE,
192
            'profile' => Logger::LEVEL_PROFILE,
193
        ];
194 24
        if (is_array($levels)) {
195 11
            $this->_levels = 0;
196 11
            foreach ($levels as $level) {
197 11
                if (isset($levelMap[$level])) {
198 11
                    $this->_levels |= $levelMap[$level];
199
                } else {
200 11
                    throw new InvalidConfigException("Unrecognized level: $level");
201
                }
202
            }
203
        } else {
204 13
            $bitmapValues = array_reduce($levelMap, function ($carry, $item) {
205 13
                return $carry | $item;
206 13
            });
207 13
            if (!($bitmapValues & $levels) && $levels !== 0) {
208 1
                throw new InvalidConfigException("Incorrect $levels value");
209
            }
210 13
            $this->_levels = $levels;
211
        }
212 24
    }
213
214
    /**
215
     * Filters the given messages according to their categories and levels.
216
     * @param array $messages messages to be filtered.
217
     * The message structure follows that in [[Logger::messages]].
218
     * @param int $levels the message levels to filter by. This is a bitmap of
219
     * level values. Value 0 means allowing all levels.
220
     * @param array $categories the message categories to filter by. If empty, it means all categories are allowed.
221
     * @param array $except the message categories to exclude. If empty, it means all categories are allowed.
222
     * @return array the filtered messages.
223
     */
224 378
    public static function filterMessages($messages, $levels = 0, $categories = [], $except = [])
225
    {
226 378
        foreach ($messages as $i => $message) {
227 378
            if ($levels && !($levels & $message[1])) {
228 353
                unset($messages[$i]);
229 353
                continue;
230
            }
231
232 298
            $matched = empty($categories);
233 298
            foreach ($categories as $category) {
234 282
                if ($message[2] === $category || !empty($category) && substr_compare($category, '*', -1, 1) === 0 && strpos($message[2], rtrim($category, '*')) === 0) {
235 238
                    $matched = true;
236 282
                    break;
237
                }
238
            }
239
240 298
            if ($matched) {
241 254
                foreach ($except as $category) {
242 3
                    $prefix = rtrim($category, '*');
243 3
                    if (($message[2] === $category || $prefix !== $category) && strpos($message[2], $prefix) === 0) {
244 3
                        $matched = false;
245 3
                        break;
246
                    }
247
                }
248
            }
249
250 298
            if (!$matched) {
251 298
                unset($messages[$i]);
252
            }
253
        }
254
255 378
        return $messages;
256
    }
257
258
    /**
259
     * Formats a log message for display as a string.
260
     * @param array $message the log message to be formatted.
261
     * The message structure follows that in [[Logger::messages]].
262
     * @return string the formatted message
263
     */
264 3
    public function formatMessage($message)
265
    {
266 3
        list($text, $level, $category, $timestamp) = $message;
267 3
        $level = Logger::getLevelName($level);
268 3
        if (!is_string($text)) {
269
            // exceptions may not be serializable if in the call stack somewhere is a Closure
270
            if ($text instanceof \Throwable || $text instanceof \Exception) {
271
                $text = (string) $text;
272
            } else {
273
                $text = VarDumper::export($text);
274
            }
275
        }
276 3
        $traces = [];
277 3
        if (isset($message[4])) {
278 2
            foreach ($message[4] as $trace) {
279
                $traces[] = "in {$trace['file']}:{$trace['line']}";
280
            }
281
        }
282
283 3
        $prefix = $this->getMessagePrefix($message);
284 3
        return $this->getTime($timestamp) . " {$prefix}[$level][$category] $text"
285 3
            . (empty($traces) ? '' : "\n    " . implode("\n    ", $traces));
286
    }
287
288
    /**
289
     * Returns a string to be prefixed to the given message.
290
     * If [[prefix]] is configured it will return the result of the callback.
291
     * The default implementation will return user IP, user ID and session ID as a prefix.
292
     * @param array $message the message being exported.
293
     * The message structure follows that in [[Logger::messages]].
294
     * @return string the prefix string
295
     */
296 9
    public function getMessagePrefix($message)
297
    {
298 9
        if ($this->prefix !== null) {
299
            return call_user_func($this->prefix, $message);
300
        }
301
302 9
        if (Yii::$app === null) {
303 1
            return '';
304
        }
305
306 8
        $request = Yii::$app->getRequest();
307 8
        $ip = $request instanceof Request ? $request->getUserIP() : '-';
308
309
        /* @var $user \yii\web\User */
310 8
        $user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
311 8
        if ($user && ($identity = $user->getIdentity(false))) {
312
            $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...
313
        } else {
314 8
            $userID = '-';
315
        }
316
317
        /* @var $session \yii\web\Session */
318 8
        $session = Yii::$app->has('session', true) ? Yii::$app->get('session') : null;
319 8
        $sessionID = $session && $session->getIsActive() ? $session->getId() : '-';
320
321 8
        return "[$ip][$userID][$sessionID]";
322
    }
323
324
    /**
325
     * Sets a value indicating whether this log target is enabled.
326
     * @param bool|callable $value a boolean value or a callable to obtain the value from.
327
     * The callable value is available since version 2.0.13.
328
     *
329
     * A callable may be used to determine whether the log target should be enabled in a dynamic way.
330
     * For example, to only enable a log if the current user is logged in you can configure the target
331
     * as follows:
332
     *
333
     * ```php
334
     * 'enabled' => function() {
335
     *     return !Yii::$app->user->isGuest;
336
     * }
337
     * ```
338
     */
339 1
    public function setEnabled($value)
340
    {
341 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...
342 1
    }
343
344
    /**
345
     * Check whether the log target is enabled.
346
     * @property bool Indicates whether this log target is enabled. Defaults to true.
347
     * @return bool A value indicating whether this log target is enabled.
348
     */
349 379
    public function getEnabled()
350
    {
351 379
        if (is_callable($this->_enabled)) {
352 1
            return call_user_func($this->_enabled, $this);
353
        }
354
355 379
        return $this->_enabled;
356
    }
357
358
    /**
359
     * Returns formatted ('Y-m-d H:i:s') timestamp for message.
360
     * If [[microtime]] is configured to true it will return format 'Y-m-d H:i:s.u'.
361
     * @param float $timestamp
362
     * @return string
363
     * @since 2.0.13
364
     */
365 3
    protected function getTime($timestamp)
366
    {
367 3
        $parts = explode('.', sprintf('%F', $timestamp));
368
369 3
        return date('Y-m-d H:i:s', $parts[0]) . ($this->microtime ? ('.' . $parts[1]) : '');
370
    }
371
}
372