Passed
Push — master ( c41b44...c53913 )
by Alexander
02:04
created

Target::getFormattedMessages()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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