Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | 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
    /**
172
     * @var bool Whether to detect infinite logging loops
173
     *
174
     * This can be disabled via {@see useLoggingLoopDetection} if you have async handlers that do not play well with this
175
     */
176
    private $detectCycles = true;
177
 
178
    /**
179
     * @psalm-param array<callable(array): array> $processors
180
     *
181
     * @param string             $name       The logging channel, a simple descriptive name that is attached to all log records
182
     * @param HandlerInterface[] $handlers   Optional stack of handlers, the first one in the array is called first, etc.
183
     * @param callable[]         $processors Optional array of processors
184
     * @param DateTimeZone|null  $timezone   Optional timezone, if not provided date_default_timezone_get() will be used
185
     */
186
    public function __construct(string $name, array $handlers = [], array $processors = [], ?DateTimeZone $timezone = null)
187
    {
188
        $this->name = $name;
189
        $this->setHandlers($handlers);
190
        $this->processors = $processors;
191
        $this->timezone = $timezone ?: new DateTimeZone(date_default_timezone_get() ?: 'UTC');
192
    }
193
 
194
    public function getName(): string
195
    {
196
        return $this->name;
197
    }
198
 
199
    /**
200
     * Return a new cloned instance with the name changed
201
     */
202
    public function withName(string $name): self
203
    {
204
        $new = clone $this;
205
        $new->name = $name;
206
 
207
        return $new;
208
    }
209
 
210
    /**
211
     * Pushes a handler on to the stack.
212
     */
213
    public function pushHandler(HandlerInterface $handler): self
214
    {
215
        array_unshift($this->handlers, $handler);
216
 
217
        return $this;
218
    }
219
 
220
    /**
221
     * Pops a handler from the stack
222
     *
223
     * @throws \LogicException If empty handler stack
224
     */
225
    public function popHandler(): HandlerInterface
226
    {
227
        if (!$this->handlers) {
228
            throw new \LogicException('You tried to pop from an empty handler stack.');
229
        }
230
 
231
        return array_shift($this->handlers);
232
    }
233
 
234
    /**
235
     * Set handlers, replacing all existing ones.
236
     *
237
     * If a map is passed, keys will be ignored.
238
     *
239
     * @param HandlerInterface[] $handlers
240
     */
241
    public function setHandlers(array $handlers): self
242
    {
243
        $this->handlers = [];
244
        foreach (array_reverse($handlers) as $handler) {
245
            $this->pushHandler($handler);
246
        }
247
 
248
        return $this;
249
    }
250
 
251
    /**
252
     * @return HandlerInterface[]
253
     */
254
    public function getHandlers(): array
255
    {
256
        return $this->handlers;
257
    }
258
 
259
    /**
260
     * Adds a processor on to the stack.
261
     */
262
    public function pushProcessor(callable $callback): self
263
    {
264
        array_unshift($this->processors, $callback);
265
 
266
        return $this;
267
    }
268
 
269
    /**
270
     * Removes the processor on top of the stack and returns it.
271
     *
272
     * @throws \LogicException If empty processor stack
273
     * @return callable
274
     */
275
    public function popProcessor(): callable
276
    {
277
        if (!$this->processors) {
278
            throw new \LogicException('You tried to pop from an empty processor stack.');
279
        }
280
 
281
        return array_shift($this->processors);
282
    }
283
 
284
    /**
285
     * @return callable[]
286
     */
287
    public function getProcessors(): array
288
    {
289
        return $this->processors;
290
    }
291
 
292
    /**
293
     * Control the use of microsecond resolution timestamps in the 'datetime'
294
     * member of new records.
295
     *
296
     * As of PHP7.1 microseconds are always included by the engine, so
297
     * there is no performance penalty and Monolog 2 enabled microseconds
298
     * by default. This function lets you disable them though in case you want
299
     * to suppress microseconds from the output.
300
     *
301
     * @param bool $micro True to use microtime() to create timestamps
302
     */
303
    public function useMicrosecondTimestamps(bool $micro): self
304
    {
305
        $this->microsecondTimestamps = $micro;
306
 
307
        return $this;
308
    }
309
 
310
    public function useLoggingLoopDetection(bool $detectCycles): self
311
    {
312
        $this->detectCycles = $detectCycles;
313
 
314
        return $this;
315
    }
316
 
317
    /**
318
     * Adds a log record.
319
     *
320
     * @param  int               $level    The logging level (a Monolog or RFC 5424 level)
321
     * @param  string            $message  The log message
322
     * @param  mixed[]           $context  The log context
323
     * @param  DateTimeImmutable $datetime Optional log date to log into the past or future
324
     * @return bool              Whether the record has been processed
325
     *
326
     * @phpstan-param Level $level
327
     */
328
    public function addRecord(int $level, string $message, array $context = [], DateTimeImmutable $datetime = null): bool
329
    {
330
        if (isset(self::RFC_5424_LEVELS[$level])) {
331
            $level = self::RFC_5424_LEVELS[$level];
332
        }
333
 
334
        if ($this->detectCycles) {
335
            $this->logDepth += 1;
336
        }
337
        if ($this->logDepth === 3) {
338
            $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.');
339
            return false;
340
        } elseif ($this->logDepth >= 5) { // log depth 4 is let through so we can log the warning above
341
            return false;
342
        }
343
 
344
        try {
345
            $record = null;
346
 
347
            foreach ($this->handlers as $handler) {
348
                if (null === $record) {
349
                    // skip creating the record as long as no handler is going to handle it
350
                    if (!$handler->isHandling(['level' => $level])) {
351
                        continue;
352
                    }
353
 
354
                    $levelName = static::getLevelName($level);
355
 
356
                    $record = [
357
                        'message' => $message,
358
                        'context' => $context,
359
                        'level' => $level,
360
                        'level_name' => $levelName,
361
                        'channel' => $this->name,
362
                        'datetime' => $datetime ?? new DateTimeImmutable($this->microsecondTimestamps, $this->timezone),
363
                        'extra' => [],
364
                    ];
365
 
366
                    try {
367
                        foreach ($this->processors as $processor) {
368
                            $record = $processor($record);
369
                        }
370
                    } catch (Throwable $e) {
371
                        $this->handleException($e, $record);
372
 
373
                        return true;
374
                    }
375
                }
376
 
377
                // once the record exists, send it to all handlers as long as the bubbling chain is not interrupted
378
                try {
379
                    if (true === $handler->handle($record)) {
380
                        break;
381
                    }
382
                } catch (Throwable $e) {
383
                    $this->handleException($e, $record);
384
 
385
                    return true;
386
                }
387
            }
388
        } finally {
389
            if ($this->detectCycles) {
390
                $this->logDepth--;
391
            }
392
        }
393
 
394
        return null !== $record;
395
    }
396
 
397
    /**
398
     * Ends a log cycle and frees all resources used by handlers.
399
     *
400
     * Closing a Handler means flushing all buffers and freeing any open resources/handles.
401
     * Handlers that have been closed should be able to accept log records again and re-open
402
     * themselves on demand, but this may not always be possible depending on implementation.
403
     *
404
     * This is useful at the end of a request and will be called automatically on every handler
405
     * when they get destructed.
406
     */
407
    public function close(): void
408
    {
409
        foreach ($this->handlers as $handler) {
410
            $handler->close();
411
        }
412
    }
413
 
414
    /**
415
     * Ends a log cycle and resets all handlers and processors to their initial state.
416
     *
417
     * Resetting a Handler or a Processor means flushing/cleaning all buffers, resetting internal
418
     * state, and getting it back to a state in which it can receive log records again.
419
     *
420
     * This is useful in case you want to avoid logs leaking between two requests or jobs when you
421
     * have a long running process like a worker or an application server serving multiple requests
422
     * in one process.
423
     */
424
    public function reset(): void
425
    {
426
        foreach ($this->handlers as $handler) {
427
            if ($handler instanceof ResettableInterface) {
428
                $handler->reset();
429
            }
430
        }
431
 
432
        foreach ($this->processors as $processor) {
433
            if ($processor instanceof ResettableInterface) {
434
                $processor->reset();
435
            }
436
        }
437
    }
438
 
439
    /**
440
     * Gets all supported logging levels.
441
     *
442
     * @return array<string, int> Assoc array with human-readable level names => level codes.
443
     * @phpstan-return array<LevelName, Level>
444
     */
445
    public static function getLevels(): array
446
    {
447
        return array_flip(static::$levels);
448
    }
449
 
450
    /**
451
     * Gets the name of the logging level.
452
     *
453
     * @throws \Psr\Log\InvalidArgumentException If level is not defined
454
     *
455
     * @phpstan-param  Level     $level
456
     * @phpstan-return LevelName
457
     */
458
    public static function getLevelName(int $level): string
459
    {
460
        if (!isset(static::$levels[$level])) {
461
            throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels)));
462
        }
463
 
464
        return static::$levels[$level];
465
    }
466
 
467
    /**
468
     * Converts PSR-3 levels to Monolog ones if necessary
469
     *
470
     * @param  string|int                        $level Level number (monolog) or name (PSR-3)
471
     * @throws \Psr\Log\InvalidArgumentException If level is not defined
472
     *
473
     * @phpstan-param  Level|LevelName|LogLevel::* $level
474
     * @phpstan-return Level
475
     */
476
    public static function toMonologLevel($level): int
477
    {
478
        if (is_string($level)) {
479
            if (is_numeric($level)) {
480
                /** @phpstan-ignore-next-line */
481
                return intval($level);
482
            }
483
 
484
            // Contains chars of all log levels and avoids using strtoupper() which may have
485
            // strange results depending on locale (for example, "i" will become "İ" in Turkish locale)
486
            $upper = strtr($level, 'abcdefgilmnortuwy', 'ABCDEFGILMNORTUWY');
487
            if (defined(__CLASS__.'::'.$upper)) {
488
                return constant(__CLASS__ . '::' . $upper);
489
            }
490
 
491
            throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels));
492
        }
493
 
494
        if (!is_int($level)) {
495
            throw new InvalidArgumentException('Level "'.var_export($level, true).'" is not defined, use one of: '.implode(', ', array_keys(static::$levels) + static::$levels));
496
        }
497
 
498
        return $level;
499
    }
500
 
501
    /**
502
     * Checks whether the Logger has a handler that listens on the given level
503
     *
504
     * @phpstan-param Level $level
505
     */
506
    public function isHandling(int $level): bool
507
    {
508
        $record = [
509
            'level' => $level,
510
        ];
511
 
512
        foreach ($this->handlers as $handler) {
513
            if ($handler->isHandling($record)) {
514
                return true;
515
            }
516
        }
517
 
518
        return false;
519
    }
520
 
521
    /**
522
     * Set a custom exception handler that will be called if adding a new record fails
523
     *
524
     * The callable will receive an exception object and the record that failed to be logged
525
     */
526
    public function setExceptionHandler(?callable $callback): self
527
    {
528
        $this->exceptionHandler = $callback;
529
 
530
        return $this;
531
    }
532
 
533
    public function getExceptionHandler(): ?callable
534
    {
535
        return $this->exceptionHandler;
536
    }
537
 
538
    /**
539
     * Adds a log record at an arbitrary level.
540
     *
541
     * This method allows for compatibility with common interfaces.
542
     *
543
     * @param mixed             $level   The log level (a Monolog, PSR-3 or RFC 5424 level)
544
     * @param string|Stringable $message The log message
545
     * @param mixed[]           $context The log context
546
     *
547
     * @phpstan-param Level|LevelName|LogLevel::* $level
548
     */
549
    public function log($level, $message, array $context = []): void
550
    {
551
        if (!is_int($level) && !is_string($level)) {
552
            throw new \InvalidArgumentException('$level is expected to be a string or int');
553
        }
554
 
555
        if (isset(self::RFC_5424_LEVELS[$level])) {
556
            $level = self::RFC_5424_LEVELS[$level];
557
        }
558
 
559
        $level = static::toMonologLevel($level);
560
 
561
        $this->addRecord($level, (string) $message, $context);
562
    }
563
 
564
    /**
565
     * Adds a log record at the DEBUG level.
566
     *
567
     * This method allows for compatibility with common interfaces.
568
     *
569
     * @param string|Stringable $message The log message
570
     * @param mixed[]           $context The log context
571
     */
572
    public function debug($message, array $context = []): void
573
    {
574
        $this->addRecord(static::DEBUG, (string) $message, $context);
575
    }
576
 
577
    /**
578
     * Adds a log record at the INFO level.
579
     *
580
     * This method allows for compatibility with common interfaces.
581
     *
582
     * @param string|Stringable $message The log message
583
     * @param mixed[]           $context The log context
584
     */
585
    public function info($message, array $context = []): void
586
    {
587
        $this->addRecord(static::INFO, (string) $message, $context);
588
    }
589
 
590
    /**
591
     * Adds a log record at the NOTICE level.
592
     *
593
     * This method allows for compatibility with common interfaces.
594
     *
595
     * @param string|Stringable $message The log message
596
     * @param mixed[]           $context The log context
597
     */
598
    public function notice($message, array $context = []): void
599
    {
600
        $this->addRecord(static::NOTICE, (string) $message, $context);
601
    }
602
 
603
    /**
604
     * Adds a log record at the WARNING level.
605
     *
606
     * This method allows for compatibility with common interfaces.
607
     *
608
     * @param string|Stringable $message The log message
609
     * @param mixed[]           $context The log context
610
     */
611
    public function warning($message, array $context = []): void
612
    {
613
        $this->addRecord(static::WARNING, (string) $message, $context);
614
    }
615
 
616
    /**
617
     * Adds a log record at the ERROR level.
618
     *
619
     * This method allows for compatibility with common interfaces.
620
     *
621
     * @param string|Stringable $message The log message
622
     * @param mixed[]           $context The log context
623
     */
624
    public function error($message, array $context = []): void
625
    {
626
        $this->addRecord(static::ERROR, (string) $message, $context);
627
    }
628
 
629
    /**
630
     * Adds a log record at the CRITICAL level.
631
     *
632
     * This method allows for compatibility with common interfaces.
633
     *
634
     * @param string|Stringable $message The log message
635
     * @param mixed[]           $context The log context
636
     */
637
    public function critical($message, array $context = []): void
638
    {
639
        $this->addRecord(static::CRITICAL, (string) $message, $context);
640
    }
641
 
642
    /**
643
     * Adds a log record at the ALERT level.
644
     *
645
     * This method allows for compatibility with common interfaces.
646
     *
647
     * @param string|Stringable $message The log message
648
     * @param mixed[]           $context The log context
649
     */
650
    public function alert($message, array $context = []): void
651
    {
652
        $this->addRecord(static::ALERT, (string) $message, $context);
653
    }
654
 
655
    /**
656
     * Adds a log record at the EMERGENCY level.
657
     *
658
     * This method allows for compatibility with common interfaces.
659
     *
660
     * @param string|Stringable $message The log message
661
     * @param mixed[]           $context The log context
662
     */
663
    public function emergency($message, array $context = []): void
664
    {
665
        $this->addRecord(static::EMERGENCY, (string) $message, $context);
666
    }
667
 
668
    /**
669
     * Sets the timezone to be used for the timestamp of log records.
670
     */
671
    public function setTimezone(DateTimeZone $tz): self
672
    {
673
        $this->timezone = $tz;
674
 
675
        return $this;
676
    }
677
 
678
    /**
679
     * Returns the timezone to be used for the timestamp of log records.
680
     */
681
    public function getTimezone(): DateTimeZone
682
    {
683
        return $this->timezone;
684
    }
685
 
686
    /**
687
     * Delegates exception management to the custom exception handler,
688
     * or throws the exception if no custom handler is set.
689
     *
690
     * @param array $record
691
     * @phpstan-param Record $record
692
     */
693
    protected function handleException(Throwable $e, array $record): void
694
    {
695
        if (!$this->exceptionHandler) {
696
            throw $e;
697
        }
698
 
699
        ($this->exceptionHandler)($e, $record);
700
    }
701
}