Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php declare(strict_types=1);
2
 
3
/*
4
 * This file is part of the Monolog package.
5
 *
6
 * (c) Jordi Boggiano <j.boggiano@seld.be>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Monolog;
13
 
14
use DateTimeZone;
15
use Monolog\Handler\HandlerInterface;
16
use Psr\Log\LoggerInterface;
17
use Psr\Log\InvalidArgumentException;
18
use Psr\Log\LogLevel;
19
use Throwable;
20
use Stringable;
21
 
22
/**
23
 * Monolog log channel
24
 *
25
 * It contains a stack of Handlers and a stack of Processors,
26
 * and uses them to store records that are added to it.
27
 *
28
 * @author Jordi Boggiano <j.boggiano@seld.be>
29
 *
30
 * @phpstan-type Level Logger::DEBUG|Logger::INFO|Logger::NOTICE|Logger::WARNING|Logger::ERROR|Logger::CRITICAL|Logger::ALERT|Logger::EMERGENCY
31
 * @phpstan-type LevelName 'DEBUG'|'INFO'|'NOTICE'|'WARNING'|'ERROR'|'CRITICAL'|'ALERT'|'EMERGENCY'
32
 * @phpstan-type Record array{message: string, context: mixed[], level: Level, level_name: LevelName, channel: string, datetime: \DateTimeImmutable, extra: mixed[]}
33
 */
34
class Logger implements LoggerInterface, ResettableInterface
35
{
36
    /**
37
     * Detailed debug information
38
     */
39
    public const DEBUG = 100;
40
 
41
    /**
42
     * Interesting events
43
     *
44
     * Examples: User logs in, SQL logs.
45
     */
46
    public const INFO = 200;
47
 
48
    /**
49
     * Uncommon events
50
     */
51
    public const NOTICE = 250;
52
 
53
    /**
54
     * Exceptional occurrences that are not errors
55
     *
56
     * Examples: Use of deprecated APIs, poor use of an API,
57
     * undesirable things that are not necessarily wrong.
58
     */
59
    public const WARNING = 300;
60
 
61
    /**
62
     * Runtime errors
63
     */
64
    public const ERROR = 400;
65
 
66
    /**
67
     * Critical conditions
68
     *
69
     * Example: Application component unavailable, unexpected exception.
70
     */
71
    public const CRITICAL = 500;
72
 
73
    /**
74
     * Action must be taken immediately
75
     *
76
     * Example: Entire website down, database unavailable, etc.
77
     * This should trigger the SMS alerts and wake you up.
78
     */
79
    public const ALERT = 550;
80
 
81
    /**
82
     * Urgent alert.
83
     */
84
    public const EMERGENCY = 600;
85
 
86
    /**
87
     * Monolog API version
88
     *
89
     * This is only bumped when API breaks are done and should
90
     * follow the major version of the library
91
     *
92
     * @var int
93
     */
94
    public const API = 2;
95
 
96
    /**
97
     * This is a static variable and not a constant to serve as an extension point for custom levels
98
     *
99
     * @var array<int, string> $levels Logging levels with the levels as key
100
     *
101
     * @phpstan-var array<Level, LevelName> $levels Logging levels with the levels as key
102
     */
103
    protected static $levels = [
104
        self::DEBUG     => 'DEBUG',
105
        self::INFO      => 'INFO',
106
        self::NOTICE    => 'NOTICE',
107
        self::WARNING   => 'WARNING',
108
        self::ERROR     => 'ERROR',
109
        self::CRITICAL  => 'CRITICAL',
110
        self::ALERT     => 'ALERT',
111
        self::EMERGENCY => 'EMERGENCY',
112
    ];
113
 
114
    /**
115
     * Mapping between levels numbers defined in RFC 5424 and Monolog ones
116
     *
117
     * @phpstan-var array<int, Level> $rfc_5424_levels
118
     */
119
    private const RFC_5424_LEVELS = [
120
        7 => self::DEBUG,
121
        6 => self::INFO,
122
        5 => self::NOTICE,
123
        4 => self::WARNING,
124
        3 => self::ERROR,
125
        2 => self::CRITICAL,
126
        1 => self::ALERT,
127
 
128
    ];
129
 
130
    /**
131
     * @var string
132
     */
133
    protected $name;
134
 
135
    /**
136
     * The handler stack
137
     *
138
     * @var HandlerInterface[]
139
     */
140
    protected $handlers;
141
 
142
    /**
143
     * Processors that will process all log records
144
     *
145
     * To process records of a single handler instead, add the processor on that specific handler
146
     *
147
     * @var callable[]
148
     */
149
    protected $processors;
150
 
151
    /**
152
     * @var bool
153
     */
154
    protected $microsecondTimestamps = true;
155
 
156
    /**
157
     * @var DateTimeZone
158
     */
159
    protected $timezone;
160
 
161
    /**
162
     * @var callable|null
163
     */
164
    protected $exceptionHandler;
165
 
166
    /**
167
     * @var int Keeps track of depth to prevent infinite logging loops
168
     */
169
    private $logDepth = 0;
170
 
171
    /**
621 lars 172
     * @var \WeakMap<\Fiber, int>|null Keeps track of depth inside fibers to prevent infinite logging loops
173
     */
174
    private $fiberLogDepth;
175
 
176
    /**
148 lars 177
     * @var bool Whether to detect infinite logging loops
178
     *
179
     * This can be disabled via {@see useLoggingLoopDetection} if you have async handlers that do not play well with this
180
     */
181
    private $detectCycles = true;
182
 
183
    /**
184
     * @psalm-param array<callable(array): array> $processors
185
     *
186
     * @param string             $name       The logging channel, a simple descriptive name that is attached to all log records
187
     * @param HandlerInterface[] $handlers   Optional stack of handlers, the first one in the array is called first, etc.
188
     * @param callable[]         $processors Optional array of processors
189
     * @param DateTimeZone|null  $timezone   Optional timezone, if not provided date_default_timezone_get() will be used
190
     */
191
    public function __construct(string $name, array $handlers = [], array $processors = [], ?DateTimeZone $timezone = null)
192
    {
193
        $this->name = $name;
194
        $this->setHandlers($handlers);
195
        $this->processors = $processors;
196
        $this->timezone = $timezone ?: new DateTimeZone(date_default_timezone_get() ?: 'UTC');
621 lars 197
 
198
        if (\PHP_VERSION_ID >= 80100) {
199
            // Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412
200
            /** @var \WeakMap<\Fiber, int> $fiberLogDepth */
201
            $fiberLogDepth = new \WeakMap();
202
            $this->fiberLogDepth = $fiberLogDepth;
203
        }
148 lars 204
    }
205
 
206
    public function getName(): string
207
    {
208
        return $this->name;
209
    }
210
 
211
    /**
212
     * Return a new cloned instance with the name changed
213
     */
214
    public function withName(string $name): self
215
    {
216
        $new = clone $this;
217
        $new->name = $name;
218
 
219
        return $new;
220
    }
221
 
222
    /**
223
     * Pushes a handler on to the stack.
224
     */
225
    public function pushHandler(HandlerInterface $handler): self
226
    {
227
        array_unshift($this->handlers, $handler);
228
 
229
        return $this;
230
    }
231
 
232
    /**
233
     * Pops a handler from the stack
234
     *
235
     * @throws \LogicException If empty handler stack
236
     */
237
    public function popHandler(): HandlerInterface
238
    {
239
        if (!$this->handlers) {
240
            throw new \LogicException('You tried to pop from an empty handler stack.');
241
        }
242
 
243
        return array_shift($this->handlers);
244
    }
245
 
246
    /**
247
     * Set handlers, replacing all existing ones.
248
     *
249
     * If a map is passed, keys will be ignored.
250
     *
251
     * @param HandlerInterface[] $handlers
252
     */
253
    public function setHandlers(array $handlers): self
254
    {
255
        $this->handlers = [];
256
        foreach (array_reverse($handlers) as $handler) {
257
            $this->pushHandler($handler);
258
        }
259
 
260
        return $this;
261
    }
262
 
263
    /**
264
     * @return HandlerInterface[]
265
     */
266
    public function getHandlers(): array
267
    {
268
        return $this->handlers;
269
    }
270
 
271
    /**
272
     * Adds a processor on to the stack.
273
     */
274
    public function pushProcessor(callable $callback): self
275
    {
276
        array_unshift($this->processors, $callback);
277
 
278
        return $this;
279
    }
280
 
281
    /**
282
     * Removes the processor on top of the stack and returns it.
283
     *
284
     * @throws \LogicException If empty processor stack
285
     * @return callable
286
     */
287
    public function popProcessor(): callable
288
    {
289
        if (!$this->processors) {
290
            throw new \LogicException('You tried to pop from an empty processor stack.');
291
        }
292
 
293
        return array_shift($this->processors);
294
    }
295
 
296
    /**
297
     * @return callable[]
298
     */
299
    public function getProcessors(): array
300
    {
301
        return $this->processors;
302
    }
303
 
304
    /**
305
     * Control the use of microsecond resolution timestamps in the 'datetime'
306
     * member of new records.
307
     *
308
     * As of PHP7.1 microseconds are always included by the engine, so
309
     * there is no performance penalty and Monolog 2 enabled microseconds
310
     * by default. This function lets you disable them though in case you want
311
     * to suppress microseconds from the output.
312
     *
313
     * @param bool $micro True to use microtime() to create timestamps
314
     */
315
    public function useMicrosecondTimestamps(bool $micro): self
316
    {
317
        $this->microsecondTimestamps = $micro;
318
 
319
        return $this;
320
    }
321
 
322
    public function useLoggingLoopDetection(bool $detectCycles): self
323
    {
324
        $this->detectCycles = $detectCycles;
325
 
326
        return $this;
327
    }
328
 
329
    /**
330
     * Adds a log record.
331
     *
332
     * @param  int               $level    The logging level (a Monolog or RFC 5424 level)
333
     * @param  string            $message  The log message
334
     * @param  mixed[]           $context  The log context
335
     * @param  DateTimeImmutable $datetime Optional log date to log into the past or future
336
     * @return bool              Whether the record has been processed
337
     *
338
     * @phpstan-param Level $level
339
     */
340
    public function addRecord(int $level, string $message, array $context = [], DateTimeImmutable $datetime = null): bool
341
    {
342
        if (isset(self::RFC_5424_LEVELS[$level])) {
343
            $level = self::RFC_5424_LEVELS[$level];
344
        }
345
 
346
        if ($this->detectCycles) {
621 lars 347
            if (\PHP_VERSION_ID >= 80100 && $fiber = \Fiber::getCurrent()) {
348
                $this->fiberLogDepth[$fiber] = $this->fiberLogDepth[$fiber] ?? 0;
349
                $logDepth = ++$this->fiberLogDepth[$fiber];
350
            } else {
351
                $logDepth = ++$this->logDepth;
352
            }
353
        } else {
354
            $logDepth = 0;
148 lars 355
        }
621 lars 356
 
357
        if ($logDepth === 3) {
148 lars 358
            $this->warning('A possible infinite logging loop was detected and aborted. It appears some of your handler code is triggering logging, see the previous log record for a hint as to what may be the cause.');
359
            return false;
621 lars 360
        } elseif ($logDepth >= 5) { // log depth 4 is let through, so we can log the warning above
148 lars 361
            return false;
362
        }
363
 
364
        try {
365
            $record = null;
366
 
367
            foreach ($this->handlers as $handler) {
368
                if (null === $record) {
369
                    // skip creating the record as long as no handler is going to handle it
370
                    if (!$handler->isHandling(['level' => $level])) {
371
                        continue;
372
                    }
373
 
374
                    $levelName = static::getLevelName($level);
375
 
376
                    $record = [
377
                        'message' => $message,
378
                        'context' => $context,
379
                        'level' => $level,
380
                        'level_name' => $levelName,
381
                        'channel' => $this->name,
382
                        'datetime' => $datetime ?? new DateTimeImmutable($this->microsecondTimestamps, $this->timezone),
383
                        'extra' => [],
384
                    ];
385
 
386
                    try {
387
                        foreach ($this->processors as $processor) {
388
                            $record = $processor($record);
389
                        }
390
                    } catch (Throwable $e) {
391
                        $this->handleException($e, $record);
392
 
393
                        return true;
394
                    }
395
                }
396
 
397
                // once the record exists, send it to all handlers as long as the bubbling chain is not interrupted
398
                try {
399
                    if (true === $handler->handle($record)) {
400
                        break;
401
                    }
402
                } catch (Throwable $e) {
403
                    $this->handleException($e, $record);
404
 
405
                    return true;
406
                }
407
            }
408
        } finally {
409
            if ($this->detectCycles) {
621 lars 410
                if (isset($fiber)) {
411
                    $this->fiberLogDepth[$fiber]--;
412
                } else {
413
                    $this->logDepth--;
414
                }
148 lars 415
            }
416
        }
417
 
418
        return null !== $record;
419
    }
420
 
421
    /**
422
     * Ends a log cycle and frees all resources used by handlers.
423
     *
424
     * Closing a Handler means flushing all buffers and freeing any open resources/handles.
425
     * Handlers that have been closed should be able to accept log records again and re-open
426
     * themselves on demand, but this may not always be possible depending on implementation.
427
     *
428
     * This is useful at the end of a request and will be called automatically on every handler
429
     * when they get destructed.
430
     */
431
    public function close(): void
432
    {
433
        foreach ($this->handlers as $handler) {
434
            $handler->close();
435
        }
436
    }
437
 
438
    /**
439
     * Ends a log cycle and resets all handlers and processors to their initial state.
440
     *
441
     * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal
442
     * state, and getting it back to a state in which it can receive log records again.
443
     *
444
     * This is useful in case you want to avoid logs leaking between two requests or jobs when you
445
     * have a long running process like a worker or an application server serving multiple requests
446
     * in one process.
447
     */
448
    public function reset(): void
449
    {
450
        foreach ($this->handlers as $handler) {
451
            if ($handler instanceof ResettableInterface) {
452
                $handler->reset();
453
            }
454
        }
455
 
456
        foreach ($this->processors as $processor) {
457
            if ($processor instanceof ResettableInterface) {
458
                $processor->reset();
459
            }
460
        }
461
    }
462
 
463
    /**
464
     * Gets all supported logging levels.
465
     *
466
     * @return array<string, int> Assoc array with human-readable level names => level codes.
467
     * @phpstan-return array<LevelName, Level>
468
     */
469
    public static function getLevels(): array
470
    {
471
        return array_flip(static::$levels);
472
    }
473
 
474
    /**
475
     * Gets the name of the logging level.
476
     *
477
     * @throws \Psr\Log\InvalidArgumentException If level is not defined
478
     *
479
     * @phpstan-param  Level     $level
480
     * @phpstan-return LevelName
481
     */
482
    public static function getLevelName(int $level): string
483
    {
484
        if (!isset(static::$levels[$level])) {
485
            throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels)));
486
        }
487
 
488
        return static::$levels[$level];
489
    }
490
 
491
    /**
492
     * Converts PSR-3 levels to Monolog ones if necessary
493
     *
494
     * @param  string|int                        $level Level number (monolog) or name (PSR-3)
495
     * @throws \Psr\Log\InvalidArgumentException If level is not defined
496
     *
497
     * @phpstan-param  Level|LevelName|LogLevel::* $level
498
     * @phpstan-return Level
499
     */
500
    public static function toMonologLevel($level): int
501
    {
502
        if (is_string($level)) {
503
            if (is_numeric($level)) {
504
                /** @phpstan-ignore-next-line */
505
                return intval($level);
506
            }
507
 
508
            // Contains chars of all log levels and avoids using strtoupper() which may have
509
            // strange results depending on locale (for example, "i" will become "İ" in Turkish locale)
510
            $upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY');
511
            if (defined(__CLASS__.'::'.$upper)) {
512
                return constant(__CLASS__ . '::' . $upper);
513
            }
514
 
515
            throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels));
516
        }
517
 
518
        if (!is_int($level)) {
519
            throw new InvalidArgumentException('Level "'.var_export($level, true).'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels));
520
        }
521
 
522
        return $level;
523
    }
524
 
525
    /**
526
     * Checks whether the Logger has a handler that listens on the given level
527
     *
528
     * @phpstan-param Level $level
529
     */
530
    public function isHandling(int $level): bool
531
    {
532
        $record = [
533
            'level' => $level,
534
        ];
535
 
536
        foreach ($this->handlers as $handler) {
537
            if ($handler->isHandling($record)) {
538
                return true;
539
            }
540
        }
541
 
542
        return false;
543
    }
544
 
545
    /**
546
     * Set a custom exception handler that will be called if adding a new record fails
547
     *
548
     * The callable will receive an exception object and the record that failed to be logged
549
     */
550
    public function setExceptionHandler(?callable $callback): self
551
    {
552
        $this->exceptionHandler = $callback;
553
 
554
        return $this;
555
    }
556
 
557
    public function getExceptionHandler(): ?callable
558
    {
559
        return $this->exceptionHandler;
560
    }
561
 
562
    /**
563
     * Adds a log record at an arbitrary level.
564
     *
565
     * This method allows for compatibility with common interfaces.
566
     *
567
     * @param mixed             $level   The log level (a Monolog, PSR-3 or RFC 5424 level)
568
     * @param string|Stringable $message The log message
569
     * @param mixed[]           $context The log context
570
     *
571
     * @phpstan-param Level|LevelName|LogLevel::* $level
572
     */
573
    public function log($level, $message, array $context = []): void
574
    {
575
        if (!is_int($level) && !is_string($level)) {
576
            throw new \InvalidArgumentException('$level is expected to be a string or int');
577
        }
578
 
579
        if (isset(self::RFC_5424_LEVELS[$level])) {
580
            $level = self::RFC_5424_LEVELS[$level];
581
        }
582
 
583
        $level = static::toMonologLevel($level);
584
 
585
        $this->addRecord($level, (string) $message, $context);
586
    }
587
 
588
    /**
589
     * Adds a log record at the DEBUG level.
590
     *
591
     * This method allows for compatibility with common interfaces.
592
     *
593
     * @param string|Stringable $message The log message
594
     * @param mixed[]           $context The log context
595
     */
596
    public function debug($message, array $context = []): void
597
    {
598
        $this->addRecord(static::DEBUG, (string) $message, $context);
599
    }
600
 
601
    /**
602
     * Adds a log record at the INFO level.
603
     *
604
     * This method allows for compatibility with common interfaces.
605
     *
606
     * @param string|Stringable $message The log message
607
     * @param mixed[]           $context The log context
608
     */
609
    public function info($message, array $context = []): void
610
    {
611
        $this->addRecord(static::INFO, (string) $message, $context);
612
    }
613
 
614
    /**
615
     * Adds a log record at the NOTICE level.
616
     *
617
     * This method allows for compatibility with common interfaces.
618
     *
619
     * @param string|Stringable $message The log message
620
     * @param mixed[]           $context The log context
621
     */
622
    public function notice($message, array $context = []): void
623
    {
624
        $this->addRecord(static::NOTICE, (string) $message, $context);
625
    }
626
 
627
    /**
628
     * Adds a log record at the WARNING level.
629
     *
630
     * This method allows for compatibility with common interfaces.
631
     *
632
     * @param string|Stringable $message The log message
633
     * @param mixed[]           $context The log context
634
     */
635
    public function warning($message, array $context = []): void
636
    {
637
        $this->addRecord(static::WARNING, (string) $message, $context);
638
    }
639
 
640
    /**
641
     * Adds a log record at the ERROR level.
642
     *
643
     * This method allows for compatibility with common interfaces.
644
     *
645
     * @param string|Stringable $message The log message
646
     * @param mixed[]           $context The log context
647
     */
648
    public function error($message, array $context = []): void
649
    {
650
        $this->addRecord(static::ERROR, (string) $message, $context);
651
    }
652
 
653
    /**
654
     * Adds a log record at the CRITICAL level.
655
     *
656
     * This method allows for compatibility with common interfaces.
657
     *
658
     * @param string|Stringable $message The log message
659
     * @param mixed[]           $context The log context
660
     */
661
    public function critical($message, array $context = []): void
662
    {
663
        $this->addRecord(static::CRITICAL, (string) $message, $context);
664
    }
665
 
666
    /**
667
     * Adds a log record at the ALERT level.
668
     *
669
     * This method allows for compatibility with common interfaces.
670
     *
671
     * @param string|Stringable $message The log message
672
     * @param mixed[]           $context The log context
673
     */
674
    public function alert($message, array $context = []): void
675
    {
676
        $this->addRecord(static::ALERT, (string) $message, $context);
677
    }
678
 
679
    /**
680
     * Adds a log record at the EMERGENCY level.
681
     *
682
     * This method allows for compatibility with common interfaces.
683
     *
684
     * @param string|Stringable $message The log message
685
     * @param mixed[]           $context The log context
686
     */
687
    public function emergency($message, array $context = []): void
688
    {
689
        $this->addRecord(static::EMERGENCY, (string) $message, $context);
690
    }
691
 
692
    /**
693
     * Sets the timezone to be used for the timestamp of log records.
694
     */
695
    public function setTimezone(DateTimeZone $tz): self
696
    {
697
        $this->timezone = $tz;
698
 
699
        return $this;
700
    }
701
 
702
    /**
703
     * Returns the timezone to be used for the timestamp of log records.
704
     */
705
    public function getTimezone(): DateTimeZone
706
    {
707
        return $this->timezone;
708
    }
709
 
710
    /**
711
     * Delegates exception management to the custom exception handler,
712
     * or throws the exception if no custom handler is set.
713
     *
714
     * @param array $record
715
     * @phpstan-param Record $record
716
     */
717
    protected function handleException(Throwable $e, array $record): void
718
    {
719
        if (!$this->exceptionHandler) {
720
            throw $e;
721
        }
722
 
723
        ($this->exceptionHandler)($e, $record);
724
    }
621 lars 725
 
726
    /**
727
     * @return array<string, mixed>
728
     */
729
    public function __serialize(): array
730
    {
731
        return [
732
            'name' => $this->name,
733
            'handlers' => $this->handlers,
734
            'processors' => $this->processors,
735
            'microsecondTimestamps' => $this->microsecondTimestamps,
736
            'timezone' => $this->timezone,
737
            'exceptionHandler' => $this->exceptionHandler,
738
            'logDepth' => $this->logDepth,
739
            'detectCycles' => $this->detectCycles,
740
        ];
741
    }
742
 
743
    /**
744
     * @param array<string, mixed> $data
745
     */
746
    public function __unserialize(array $data): void
747
    {
748
        foreach (['name', 'handlers', 'processors', 'microsecondTimestamps', 'timezone', 'exceptionHandler', 'logDepth', 'detectCycles'] as $property) {
749
            if (isset($data[$property])) {
750
                $this->$property = $data[$property];
751
            }
752
        }
753
 
754
        if (\PHP_VERSION_ID >= 80100) {
755
            // Local variable for phpstan, see https://github.com/phpstan/phpstan/issues/6732#issuecomment-1111118412
756
            /** @var \WeakMap<\Fiber, int> $fiberLogDepth */
757
            $fiberLogDepth = new \WeakMap();
758
            $this->fiberLogDepth = $fiberLogDepth;
759
        }
760
    }
148 lars 761
}