Passed
Pull Request — master (#51)
by Evgeniy
01:56
created

Target::enable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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