Completed
Push — 2.1 ( 4d9204...3e6f8b )
by
unknown
11:56
created

Target::formatMessage()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.0092

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 11
cts 12
cp 0.9167
rs 9.2
c 0
b 0
f 0
cc 4
eloc 12
nc 4
nop 1
crap 4.0092
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\LogLevel;
11
use Yii;
12
use yii\base\Component;
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
 * For more details and usage information on Target, see the [guide article on logging & targets](guide:runtime-logging).
29
 *
30
 * @author Qiang Xue <[email protected]>
31
 * @since 2.0
32
 */
33
abstract class Target extends Component
34
{
35
    /**
36
     * @var bool whether to enable this log target. Defaults to true.
37
     */
38
    public $enabled = true;
39
    /**
40
     * @var array list of message categories that this target is interested in. Defaults to empty, meaning all categories.
41
     * You can use an asterisk at the end of a category so that the category may be used to
42
     * match those categories sharing the same common prefix. For example, 'yii\db\*' will match
43
     * categories starting with 'yii\db\', such as `yii\db\Connection`.
44
     */
45
    public $categories = [];
46
    /**
47
     * @var array list of message categories that this target is NOT interested in. Defaults to empty, meaning no uninteresting messages.
48
     * If this property is not empty, then any category listed here will be excluded from [[categories]].
49
     * You can use an asterisk at the end of a category so that the category can be used to
50
     * match those categories sharing the same common prefix. For example, 'yii\db\*' will match
51
     * categories starting with 'yii\db\', such as `yii\db\Connection`.
52
     * @see categories
53
     */
54
    public $except = [];
55
    /**
56
     * @var array the message levels that this target is interested in.
57
     *
58
     * The parameter should be an array of interested level names. See [[LogLevel]] constants for valid level names.
59
     *
60
     * For example:
61
     *
62
     * ```php
63
     * ['error', 'warning'],
64
     * // or
65
     * [LogLevel::ERROR, LogLevel::WARNING]
66
     * ```
67
     *
68
     * Defaults is empty array, meaning all available levels.
69
     */
70
    public $levels = [];
71
    /**
72
     * @var array list of the PHP predefined variables that should be logged in a message.
73
     * Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be logged.
74
     *
75
     * Defaults to `['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER']`.
76
     *
77
     * Since version 2.0.9 additional syntax can be used:
78
     * Each element could be specified as one of the following:
79
     *
80
     * - `var` - `var` will be logged.
81
     * - `var.key` - only `var[key]` key will be logged.
82
     * - `!var.key` - `var[key]` key will be excluded.
83
     *
84
     * @see \yii\helpers\ArrayHelper::filter()
85
     */
86
    public $logVars = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'];
87
    /**
88
     * @var callable a PHP callable that returns a string to be prefixed to every exported message.
89
     *
90
     * If not set, [[getMessagePrefix()]] will be used, which prefixes the message with context information
91
     * such as user IP, user ID and session ID.
92
     *
93
     * The signature of the callable should be `function ($message)`.
94
     */
95
    public $prefix;
96
    /**
97
     * @var int how many messages should be accumulated before they are exported.
98
     * Defaults to 1000. Note that messages will always be exported when the application terminates.
99
     * Set this property to be 0 if you don't want to export messages until the application terminates.
100
     */
101
    public $exportInterval = 1000;
102
    /**
103
     * @var array the messages that are retrieved from the logger so far by this log target.
104
     * Please refer to [[Logger::messages]] for the details about the message structure.
105
     */
106
    public $messages = [];
107
108
109
    /**
110
     * Exports log [[messages]] to a specific destination.
111
     * Child classes must implement this method.
112
     */
113
    abstract public function export();
114
115
    /**
116
     * Processes the given log messages.
117
     * This method will filter the given messages with [[levels]] and [[categories]].
118
     * And if requested, it will also export the filtering result to specific medium (e.g. email).
119
     * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure
120
     * of each message.
121
     * @param bool $final whether this method is called at the end of the current application
122
     */
123 31
    public function collect($messages, $final)
124
    {
125 31
        $this->messages = array_merge($this->messages, static::filterMessages($messages, $this->levels, $this->categories, $this->except));
126 31
        $count = count($this->messages);
127 31
        if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) {
128 25
            if (($context = $this->getContextMessage()) !== '') {
129 6
                $this->messages[] = [LogLevel::INFO, $context, ['category' => 'application', 'time' => YII_BEGIN_TIME]];
130
            }
131
            // set exportInterval to 0 to avoid triggering export again while exporting
132 25
            $oldExportInterval = $this->exportInterval;
133 25
            $this->exportInterval = 0;
134 25
            $this->export();
135 25
            $this->exportInterval = $oldExportInterval;
136
137 25
            $this->messages = [];
138
        }
139 31
    }
140
141
    /**
142
     * Generates the context information to be logged.
143
     * The default implementation will dump user information, system variables, etc.
144
     * @return string the context information. If an empty string, it means no context information.
145
     */
146 26
    protected function getContextMessage()
147
    {
148 26
        $context = ArrayHelper::filter($GLOBALS, $this->logVars);
149 26
        $result = [];
150 26
        foreach ($context as $key => $value) {
151 7
            $result[] = "\${$key} = " . VarDumper::dumpAsString($value);
152
        }
153 26
        return implode("\n\n", $result);
154
    }
155
156
    /**
157
     * Filters the given messages according to their categories and levels.
158
     * @param array $messages messages to be filtered.
159
     * The message structure follows that in [[Logger::messages]].
160
     * @param array $levels the message levels to filter by. Empty value means allowing all levels.
161
     * @param array $categories the message categories to filter by. If empty, it means all categories are allowed.
162
     * @param array $except the message categories to exclude. If empty, it means all categories are allowed.
163
     * @return array the filtered messages.
164
     */
165 31
    public static function filterMessages($messages, $levels = [], $categories = [], $except = [])
166
    {
167 31
        foreach ($messages as $i => $message) {
168 31
            if (!empty($levels) && !in_array($message[0], $levels, true)) {
169 18
                unset($messages[$i]);
170 18
                continue;
171
            }
172
173 26
            $matched = empty($categories);
174 26
            foreach ($categories as $category) {
175 11
                if ($message[2]['category'] === $category || !empty($category) && substr_compare($category, '*', -1, 1) === 0 && strpos($message[2]['category'], rtrim($category, '*')) === 0) {
176 10
                    $matched = true;
177 11
                    break;
178
                }
179
            }
180
181 26
            if ($matched) {
182 25
                foreach ($except as $category) {
183 1
                    $prefix = rtrim($category, '*');
184 1
                    if (($message[2]['category'] === $category || $prefix !== $category) && strpos($message[2]['category'], $prefix) === 0) {
185 1
                        $matched = false;
186 1
                        break;
187
                    }
188
                }
189
            }
190
191 26
            if (!$matched) {
192 26
                unset($messages[$i]);
193
            }
194
        }
195 31
        return $messages;
196
    }
197
198
    /**
199
     * Formats a log message for display as a string.
200
     * @param array $message the log message to be formatted.
201
     * The message structure follows that in [[Logger::messages]].
202
     * @return string the formatted message
203
     */
204 2
    public function formatMessage($message)
205
    {
206 2
        [$level, $text, $context] = $message;
0 ignored issues
show
Bug introduced by
The variable $level seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $context does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $text does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
207 2
        $category = $context['category'];
208 2
        $timestamp = $context['time'];
209 2
        $level = Logger::getLevelName($level);
0 ignored issues
show
Bug introduced by
The variable $level seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
210 2
        $traces = [];
211 2
        if (isset($context['trace'])) {
212 2
            foreach ($context['trace'] as $trace) {
213
                $traces[] = "in {$trace['file']}:{$trace['line']}";
214
            }
215
        }
216
217 2
        $prefix = $this->getMessagePrefix($message);
218 2
        return date('Y-m-d H:i:s', $timestamp) . " {$prefix}[$level][$category] $text"
219 2
            . (empty($traces) ? '' : "\n    " . implode("\n    ", $traces));
220
    }
221
222
    /**
223
     * Returns a string to be prefixed to the given message.
224
     * If [[prefix]] is configured it will return the result of the callback.
225
     * The default implementation will return user IP, user ID and session ID as a prefix.
226
     * @param array $message the message being exported.
227
     * The message structure follows that in [[Logger::messages]].
228
     * @return string the prefix string
229
     */
230 8
    public function getMessagePrefix($message)
231
    {
232 8
        if ($this->prefix !== null) {
233
            return call_user_func($this->prefix, $message);
234
        }
235
236 8
        if (Yii::$app === null) {
237
            return '';
238
        }
239
240 8
        $request = Yii::$app->getRequest();
241 8
        $ip = $request instanceof Request ? $request->getUserIP() : '-';
242
243
        /* @var $user \yii\web\User */
244 8
        $user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
245 8
        if ($user && ($identity = $user->getIdentity(false))) {
246
            $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...
247
        } else {
248 8
            $userID = '-';
249
        }
250
251
        /* @var $session \yii\web\Session */
252 8
        $session = Yii::$app->has('session', true) ? Yii::$app->get('session') : null;
253 8
        $sessionID = $session && $session->getIsActive() ? $session->getId() : '-';
254
255 8
        return "[$ip][$userID][$sessionID]";
256
    }
257
}
258