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

Target::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
ccs 4
cts 4
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 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_callable;
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 122
    public function __construct()
81
    {
82 122
        $this->categories = new MessageCategoryFilter();
83 122
        $this->messages = new MessageCollection();
84 122
        $this->formatter = new MessageFormatter();
85 122
    }
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 54
    public function collect(array $messages, bool $final): void
98
    {
99 54
        $this->messages->addMultiple($this->filterMessages($messages));
100 50
        $count = $this->messages->count();
101
102 50
        if ($count > 0 && ($final || ($this->exportInterval > 0 && $count >= $this->exportInterval))) {
103 8
            if (($contextMessage = $this->getContextMessage()) !== '') {
104 8
                $this->messages->add(LogLevel::INFO, $contextMessage, [
105 8
                    'category' => MessageCategoryFilter::DEFAULT,
106 8
                    'time' => microtime(true),
107
                ]);
108
            }
109
            // set exportInterval to 0 to avoid triggering export again while exporting
110 8
            $oldExportInterval = $this->exportInterval;
111 8
            $this->exportInterval = 0;
112 8
            $this->export();
113 8
            $this->exportInterval = $oldExportInterval;
114 8
            $this->messages->clear();
115
        }
116 50
    }
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 27
    public function setLogVars(array $logVars): self
181
    {
182 27
        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 21
        $this->logVars = $logVars;
192 21
        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 8
    public function setFormat(callable $format): self
205
    {
206 8
        $this->formatter->setFormat($format);
207 8
        return $this;
208
    }
209
210
    /**
211
     * Formats all log messages for display as a string.
212
     *
213
     * @return string
214
     */
215 12
    public function formatMessages(): string
216
    {
217 12
        $formatted = '';
218
219 12
        foreach ($this->messages->all() as $message) {
220 12
            $formatted .= $this->formatter->format($message);
221
        }
222
223
        return $formatted;
224
    }
225
226
    /**
227
     * Gets a list of log messages that are retrieved from the logger so far by this log target.
228
     *
229
     * @return array[] The list of log messages.
230
     *
231
     * @see MessageCollection::$messages
232
     */
233 37
    public function getMessages(): array
234
    {
235 37
        return $this->messages->all();
236
    }
237
238
    /**
239
     * Sets a PHP callable that returns a string to be prefixed to every exported message.
240
     *
241
     * @param callable $prefix The PHP callable to get a string prefix of the log message.
242
     *
243
     * @return self
244
     *
245
     * @see MessageFormatter::$prefix
246
     */
247 8
    public function setPrefix(callable $prefix): self
248
    {
249 8
        $this->formatter->setPrefix($prefix);
250 8
        return $this;
251
    }
252
253
    /**
254
     * Sets how many messages should be accumulated before they are exported.
255
     *
256
     * @param int $exportInterval The number of log messages to accumulate before exporting.
257
     *
258
     * @return self
259
     *
260
     * @see Target::$exportInterval
261
     */
262
    public function setExportInterval(int $exportInterval): self
263
    {
264
        $this->exportInterval = $exportInterval;
265
        return $this;
266
    }
267
268
    /**
269
     * Sets a date format for the log timestamp.
270
     *
271
     * @param string $format The date format for the log timestamp.
272
     *
273
     * @return self
274
     *
275
     * @see Target::$timestampFormat
276
     */
277 3
    public function setTimestampFormat(string $format): self
278
    {
279 3
        $this->formatter->setTimestampFormat($format);
280 3
        return $this;
281
    }
282
283
    /**
284
     * Sets a PHP callable that returns a boolean indicating whether this log target is enabled.
285
     *
286
     * @param callable $value The PHP callable to get a boolean value.
287
     *
288
     * @return self
289
     *
290
     * @see Target::$enabled
291
     */
292 7
    public function setEnabled(callable $value): self
293
    {
294 7
        $this->enabled = $value;
295 7
        return $this;
296
    }
297
298
    /**
299
     * Enables the log target.
300
     *
301
     * @return self
302
     *
303
     * @see Target::$enabled
304
     */
305 1
    public function enable(): self
306
    {
307 1
        $this->enabled = true;
308 1
        return $this;
309
    }
310
311
    /**
312
     * Disables the log target.
313
     *
314
     * @return self
315
     *
316
     * @see Target::$enabled
317
     */
318 1
    public function disable(): self
319
    {
320 1
        $this->enabled = false;
321 1
        return $this;
322
    }
323
324
    /**
325
     * Check whether the log target is enabled.
326
     *
327
     * @throws LogRuntimeException for a callable "enabled" that does not return a boolean.
328
     *
329
     * @return bool The value indicating whether this log target is enabled.
330
     *
331
     * @see Target::$enabled
332
     */
333 30
    public function isEnabled(): bool
334
    {
335 30
        if (is_bool($this->enabled)) {
336 24
            return $this->enabled;
337
        }
338
339 7
        if (!is_bool($enabled = ($this->enabled)($this))) {
340 6
            throw new LogRuntimeException(sprintf(
341 6
                'The PHP callable "enabled" must returns a boolean, %s received.',
342 6
                gettype($enabled)
343
            ));
344
        }
345
346 1
        return $enabled;
347
    }
348
349
    /**
350
     * Generates the context information to be logged.
351
     *
352
     * The default implementation will dump user information, system variables, etc.
353
     *
354
     * @return string The context information. If an empty string, it means no context information.
355
     */
356 8
    private function getContextMessage(): string
357
    {
358 8
        $result = [];
359
360 8
        foreach (ArrayHelper::filter($GLOBALS, $this->logVars) as $key => $value) {
361 8
            $result[] = "\${$key} = " . VarDumper::create($value)->asString();
362
        }
363
364 8
        return implode("\n\n", $result);
365
    }
366
367
    /**
368
     * Filters the given messages according to their categories and levels.
369
     *
370
     * The message structure follows that in {@see MessageCollection::$messages}.
371
     *
372
     * @param array[] $messages List log messages to be filtered.
373
     *
374
     * @return array[] The filtered log messages.
375
     */
376 54
    private function filterMessages(array $messages): array
377
    {
378 54
        foreach ($messages as $i => $message) {
379 54
            $levels = $this->messages->getLevels();
380
381 54
            if ((!empty($levels) && !in_array(($message[0] ?? ''), $levels, true))) {
382 7
                unset($messages[$i]);
383 7
                continue;
384
            }
385
386 54
            $category = (string) ($message[2]['category'] ?? '');
387
388 54
            if ($this->categories->isExcluded($category)) {
389 13
                unset($messages[$i]);
390
            }
391
        }
392
393 54
        return $messages;
394
    }
395
}
396