Passed
Push — master ( c333cb...c41b44 )
by Evgeniy
02:19
created

Target::getTimestampFormat()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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