Passed
Pull Request — master (#51)
by Evgeniy
02:11
created

Target   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 380
Duplicated Lines 0 %

Test Coverage

Coverage 90.22%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 77
dl 0
loc 380
ccs 83
cts 92
cp 0.9022
rs 9.6
c 2
b 0
f 0
wmc 35

19 Methods

Rating   Name   Duplication   Size   Complexity  
A setFormat() 0 4 1
A setExcept() 0 4 1
A collect() 0 18 6
A setLevels() 0 4 1
A setCategories() 0 4 1
A setLogVars() 0 13 3
A __construct() 0 5 1
A enable() 0 4 1
A disable() 0 4 1
A setTimestampFormat() 0 4 1
A setEnabled() 0 4 1
A setExportInterval() 0 4 1
A filterMessages() 0 18 5
A formatMessages() 0 9 2
A setPrefix() 0 4 1
A getMessages() 0 3 1
A getFormattedMessages() 0 9 2
A isEnabled() 0 14 3
A getContextMessage() 0 9 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Log;
6
7
use Psr\Log\InvalidArgumentException;
8
use Psr\Log\LogLevel;
9
use Yiisoft\Arrays\ArrayHelper;
10
use Yiisoft\VarDumper\VarDumper;
11
12
use function gettype;
13
use function implode;
14
use function in_array;
15
use function is_bool;
16
use function is_string;
17
use function microtime;
18
use function sprintf;
19
20
/**
21
 * Target is the base class for all log target classes.
22
 *
23
 * A log target object will filter the messages logged by {@see \Yiisoft\Log\Logger} according
24
 * to its {@see Target::setLevels()} and {@see Target::setCategories()}. It may also export
25
 * the filtered messages to specific destination defined by the target, such as emails, files.
26
 *
27
 * Level filter and category filter are combinatorial, i.e., only messages
28
 * satisfying both filter conditions will be handled. Additionally, you may
29
 * specify {@see Target::setExcept()} to exclude messages of certain categories.
30
 */
31
abstract class Target
32
{
33
    private MessageCategoryFilter $categories;
34
    private MessageCollection $messages;
35
    private MessageFormatter $formatter;
36
37
    /**
38
     * @var string[] list of the PHP predefined variables that should be logged in a message.
39
     *
40
     * Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be logged.
41
     *
42
     * Defaults to `['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER']`.
43
     *
44
     * Each element can also be specified as one of the following:
45
     *
46
     * - `var` - `var` will be logged.
47
     * - `var.key` - only `var[key]` key will be logged.
48
     * - `!var.key` - `var[key]` key will be excluded.
49
     *
50
     * Note that if you need $_SESSION to logged regardless if session
51
     * was used you have to open it right at he start of your request.
52
     *
53
     * @see \Yiisoft\Arrays\ArrayHelper::filter()
54
     */
55
    private array $logVars = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER'];
56
57
    /**
58
     * @var int How many log messages should be accumulated before they are exported.
59
     *
60
     * Defaults to 1000. Note that messages will always be exported when the application terminates.
61
     * Set this property to be 0 if you don't want to export messages until the application terminates.
62
     */
63
    private int $exportInterval = 1000;
64
65
    /**
66
     * @var bool|callable Enables or disables the current target to export.
67
     */
68
    private $enabled = true;
69
70
    /**
71
     * Exports log messages to a specific destination.
72
     * Child classes must implement this method.
73
     */
74
    abstract protected function export(): void;
75
76
    /**
77
     * When defining a constructor in child classes, you must call `parent::__construct()`.
78
     */
79 124
    public function __construct()
80
    {
81 124
        $this->categories = new MessageCategoryFilter();
82 124
        $this->messages = new MessageCollection();
83 124
        $this->formatter = new MessageFormatter();
84 124
    }
85
86
    /**
87
     * Processes the given log messages.
88
     *
89
     * This method will filter the given messages with levels and categories.
90
     * The message structure follows that in {@see MessageCollection::$messages}.
91
     * And if requested, it will also export the filtering result to specific medium (e.g. email).
92
     *
93
     * @param array $messages Log messages to be processed.
94
     * @param bool $final Whether this method is called at the end of the current application.
95
     */
96 56
    public function collect(array $messages, bool $final): void
97
    {
98 56
        $this->messages->addMultiple($this->filterMessages($messages));
99 52
        $count = $this->messages->count();
100
101 52
        if ($count > 0 && ($final || ($this->exportInterval > 0 && $count >= $this->exportInterval))) {
102 10
            if (($contextMessage = $this->getContextMessage()) !== '') {
103 10
                $this->messages->add(LogLevel::INFO, $contextMessage, [
104 10
                    'category' => MessageCategoryFilter::DEFAULT,
105 10
                    'time' => microtime(true),
106
                ]);
107
            }
108
            // set exportInterval to 0 to avoid triggering export again while exporting
109 10
            $oldExportInterval = $this->exportInterval;
110 10
            $this->exportInterval = 0;
111 10
            $this->export();
112 10
            $this->exportInterval = $oldExportInterval;
113 10
            $this->messages->clear();
114
        }
115 52
    }
116
117
    /**
118
     * Sets a list of log message categories that this target is interested in.
119
     *
120
     * @param array $categories The list of log message categories.
121
     *
122
     * @throws InvalidArgumentException for invalid log message categories structure.
123
     *
124
     * @return self
125
     *
126
     * @see MessageCategoryFilter::$include
127
     */
128 18
    public function setCategories(array $categories): self
129
    {
130 18
        $this->categories->include($categories);
131 12
        return $this;
132
    }
133
134
    /**
135
     * Sets a list of log message categories that this target is NOT interested in.
136
     *
137
     * @param array $except The list of log message categories.
138
     *
139
     * @throws InvalidArgumentException for invalid log message categories structure.
140
     *
141
     * @return self
142
     *
143
     * @see MessageCategoryFilter::$exclude
144
     */
145 9
    public function setExcept(array $except): self
146
    {
147 9
        $this->categories->exclude($except);
148 3
        return $this;
149
    }
150
151
    /**
152
     * Sets a list of log message levels that current target is interested in.
153
     *
154
     * @param array $levels The list of log message levels.
155
     *
156
     * @throws InvalidArgumentException for invalid log message level.
157
     *
158
     * @return self
159
     *
160
     * @see MessageCollection::$levels
161
     */
162 15
    public function setLevels(array $levels): self
163
    {
164 15
        $this->messages->setLevels($levels);
165 9
        return $this;
166
    }
167
168
    /**
169
     * Sets a list of the PHP predefined variables that should be logged in a message.
170
     *
171
     * @param array $logVars The list of PHP predefined variables.
172
     *
173
     * @throws InvalidArgumentException for non-string values.
174
     *
175
     * @return self
176
     *
177
     * @see Target::$logVars
178
     */
179 27
    public function setLogVars(array $logVars): self
180
    {
181 27
        foreach ($logVars as $logVar) {
182 7
            if (!is_string($logVar)) {
183 6
                throw new InvalidArgumentException(sprintf(
184 6
                    'The PHP predefined variable must be a string, %s received.',
185 6
                    gettype($logVar)
186
                ));
187
            }
188
        }
189
190 21
        $this->logVars = $logVars;
191 21
        return $this;
192
    }
193
194
    /**
195
     * Sets a PHP callable that returns a string representation of the log message.
196
     *
197
     * @param callable $format The PHP callable to get a string value from.
198
     *
199
     * @return self
200
     *
201
     * @see MessageFormatter::$format
202
     */
203 10
    public function setFormat(callable $format): self
204
    {
205 10
        $this->formatter->setFormat($format);
206 10
        return $this;
207
    }
208
209
    /**
210
     * Sets a PHP callable that returns a string to be prefixed to every exported message.
211
     *
212
     * @param callable $prefix The PHP callable to get a string prefix of the log message.
213
     *
214
     * @return self
215
     *
216
     * @see MessageFormatter::$prefix
217
     */
218 10
    public function setPrefix(callable $prefix): self
219
    {
220 10
        $this->formatter->setPrefix($prefix);
221 10
        return $this;
222
    }
223
224
    /**
225
     * Sets how many messages should be accumulated before they are exported.
226
     *
227
     * @param int $exportInterval The number of log messages to accumulate before exporting.
228
     *
229
     * @return self
230
     *
231
     * @see Target::$exportInterval
232
     */
233
    public function setExportInterval(int $exportInterval): self
234
    {
235
        $this->exportInterval = $exportInterval;
236
        return $this;
237
    }
238
239
    /**
240
     * Sets a date format for the log timestamp.
241
     *
242
     * @param string $format The date format for the log timestamp.
243
     *
244
     * @return self
245
     *
246
     * @see Target::$timestampFormat
247
     */
248 3
    public function setTimestampFormat(string $format): self
249
    {
250 3
        $this->formatter->setTimestampFormat($format);
251 3
        return $this;
252
    }
253
254
    /**
255
     * Sets a PHP callable that returns a boolean indicating whether this log target is enabled.
256
     *
257
     * @param callable $value The PHP callable to get a boolean value.
258
     *
259
     * @return self
260
     *
261
     * @see Target::$enabled
262
     */
263 7
    public function setEnabled(callable $value): self
264
    {
265 7
        $this->enabled = $value;
266 7
        return $this;
267
    }
268
269
    /**
270
     * Enables the log target.
271
     *
272
     * @return self
273
     *
274
     * @see Target::$enabled
275
     */
276 1
    public function enable(): self
277
    {
278 1
        $this->enabled = true;
279 1
        return $this;
280
    }
281
282
    /**
283
     * Disables the log target.
284
     *
285
     * @return self
286
     *
287
     * @see Target::$enabled
288
     */
289 1
    public function disable(): self
290
    {
291 1
        $this->enabled = false;
292 1
        return $this;
293
    }
294
295
    /**
296
     * Check whether the log target is enabled.
297
     *
298
     * @throws LogRuntimeException for a callable "enabled" that does not return a boolean.
299
     *
300
     * @return bool The value indicating whether this log target is enabled.
301
     *
302
     * @see Target::$enabled
303
     */
304 30
    public function isEnabled(): bool
305
    {
306 30
        if (is_bool($this->enabled)) {
307 24
            return $this->enabled;
308
        }
309
310 7
        if (!is_bool($enabled = ($this->enabled)($this))) {
311 6
            throw new LogRuntimeException(sprintf(
312 6
                'The PHP callable "enabled" must returns a boolean, %s received.',
313 6
                gettype($enabled)
314
            ));
315
        }
316
317 1
        return $enabled;
318
    }
319
320
    /**
321
     * Gets a list of log messages that are retrieved from the logger so far by this log target.
322
     *
323
     * @return array[] The list of log messages.
324
     *
325
     * @see MessageCollection::$messages
326
     */
327 39
    protected function getMessages(): array
328
    {
329 39
        return $this->messages->all();
330
    }
331
332
    /**
333
     * Gets a list of formatted log messages.
334
     *
335
     * @return array The list of formatted log messages.
336
     */
337
    protected function getFormattedMessages(): array
338
    {
339
        $formatted = [];
340
341
        foreach ($this->messages->all() as $key => $message) {
342
            $formatted[$key] = $this->formatter->format($message);
343
        }
344
345
        return $formatted;
346
    }
347
348
    /**
349
     * Formats all log messages for display as a string.
350
     *
351
     * @param string $separator The log messages string separator.
352
     *
353
     * @return string The string formatted log messages.
354
     */
355 12
    protected function formatMessages(string $separator = ''): string
356
    {
357 12
        $formatted = '';
358
359 12
        foreach ($this->messages->all() as $message) {
360 12
            $formatted .= $this->formatter->format($message) . $separator;
361
        }
362
363
        return $formatted;
364
    }
365
366
    /**
367
     * Generates the context information to be logged.
368
     *
369
     * The default implementation will dump user information, system variables, etc.
370
     *
371
     * @return string The context information. If an empty string, it means no context information.
372
     */
373 10
    private function getContextMessage(): string
374
    {
375 10
        $result = [];
376
377 10
        foreach (ArrayHelper::filter($GLOBALS, $this->logVars) as $key => $value) {
378 10
            $result[] = "\${$key} = " . VarDumper::create($value)->asString();
379
        }
380
381 10
        return implode("\n\n", $result);
382
    }
383
384
    /**
385
     * Filters the given messages according to their categories and levels.
386
     *
387
     * The message structure follows that in {@see MessageCollection::$messages}.
388
     *
389
     * @param array[] $messages List log messages to be filtered.
390
     *
391
     * @return array[] The filtered log messages.
392
     */
393 56
    private function filterMessages(array $messages): array
394
    {
395 56
        foreach ($messages as $i => $message) {
396 56
            $levels = $this->messages->getLevels();
397
398 56
            if ((!empty($levels) && !in_array(($message[0] ?? ''), $levels, true))) {
399 7
                unset($messages[$i]);
400 7
                continue;
401
            }
402
403 56
            $category = (string) ($message[2]['category'] ?? '');
404
405 56
            if ($this->categories->isExcluded($category)) {
406 13
                unset($messages[$i]);
407
            }
408
        }
409
410 56
        return $messages;
411
    }
412
}
413