Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
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 Symfony\Component\HttpKernel\Log;
13
 
14
use Psr\Log\AbstractLogger;
15
use Psr\Log\InvalidArgumentException;
16
use Psr\Log\LogLevel;
17
use Symfony\Component\HttpFoundation\Request;
18
use Symfony\Component\HttpFoundation\RequestStack;
19
 
20
/**
21
 * Minimalist PSR-3 logger designed to write in stderr or any other stream.
22
 *
23
 * @author Kévin Dunglas <dunglas@gmail.com>
24
 */
25
class Logger extends AbstractLogger implements DebugLoggerInterface
26
{
27
    private const LEVELS = [
28
        LogLevel::DEBUG => 0,
29
        LogLevel::INFO => 1,
30
        LogLevel::NOTICE => 2,
31
        LogLevel::WARNING => 3,
32
        LogLevel::ERROR => 4,
33
        LogLevel::CRITICAL => 5,
34
        LogLevel::ALERT => 6,
35
        LogLevel::EMERGENCY => 7,
36
    ];
37
    private const PRIORITIES = [
38
        LogLevel::DEBUG => 100,
39
        LogLevel::INFO => 200,
40
        LogLevel::NOTICE => 250,
41
        LogLevel::WARNING => 300,
42
        LogLevel::ERROR => 400,
43
        LogLevel::CRITICAL => 500,
44
        LogLevel::ALERT => 550,
45
        LogLevel::EMERGENCY => 600,
46
    ];
47
 
48
    private int $minLevelIndex;
49
    private \Closure $formatter;
50
    private bool $debug = false;
51
    private array $logs = [];
52
    private array $errorCount = [];
53
 
54
    /** @var resource|null */
55
    private $handle;
56
 
57
    /**
58
     * @param string|resource|null $output
59
     */
60
    public function __construct(string $minLevel = null, $output = null, callable $formatter = null, private readonly ?RequestStack $requestStack = null)
61
    {
62
        if (null === $minLevel) {
63
            $minLevel = null === $output || 'php://stdout' === $output || 'php://stderr' === $output ? LogLevel::ERROR : LogLevel::WARNING;
64
 
65
            if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) {
66
                $minLevel = match ((int) ($_ENV['SHELL_VERBOSITY'] ?? $_SERVER['SHELL_VERBOSITY'])) {
67
                    -1 => LogLevel::ERROR,
68
                    1 => LogLevel::NOTICE,
69
                    2 => LogLevel::INFO,
70
                    3 => LogLevel::DEBUG,
71
                    default => $minLevel,
72
                };
73
            }
74
        }
75
 
76
        if (!isset(self::LEVELS[$minLevel])) {
77
            throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $minLevel));
78
        }
79
 
80
        $this->minLevelIndex = self::LEVELS[$minLevel];
81
        $this->formatter = null !== $formatter ? $formatter(...) : $this->format(...);
82
        if ($output && false === $this->handle = \is_resource($output) ? $output : @fopen($output, 'a')) {
83
            throw new InvalidArgumentException(sprintf('Unable to open "%s".', $output));
84
        }
85
    }
86
 
87
    public function enableDebug(): void
88
    {
89
        $this->debug = true;
90
    }
91
 
92
    public function log($level, $message, array $context = []): void
93
    {
94
        if (!isset(self::LEVELS[$level])) {
95
            throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
96
        }
97
 
98
        if (self::LEVELS[$level] < $this->minLevelIndex) {
99
            return;
100
        }
101
 
102
        $formatter = $this->formatter;
103
        if ($this->handle) {
104
            @fwrite($this->handle, $formatter($level, $message, $context).\PHP_EOL);
105
        } else {
106
            error_log($formatter($level, $message, $context, false));
107
        }
108
 
109
        if ($this->debug && $this->requestStack) {
110
            $this->record($level, $message, $context);
111
        }
112
    }
113
 
114
    public function getLogs(Request $request = null): array
115
    {
116
        if ($request) {
117
            return $this->logs[spl_object_id($request)] ?? [];
118
        }
119
 
120
        return array_merge(...array_values($this->logs));
121
    }
122
 
123
    public function countErrors(Request $request = null): int
124
    {
125
        if ($request) {
126
            return $this->errorCount[spl_object_id($request)] ?? 0;
127
        }
128
 
129
        return array_sum($this->errorCount);
130
    }
131
 
132
    public function clear(): void
133
    {
134
        $this->logs = [];
135
        $this->errorCount = [];
136
    }
137
 
138
    private function format(string $level, string $message, array $context, bool $prefixDate = true): string
139
    {
140
        if (str_contains($message, '{')) {
141
            $replacements = [];
142
            foreach ($context as $key => $val) {
143
                if (null === $val || \is_scalar($val) || $val instanceof \Stringable) {
144
                    $replacements["{{$key}}"] = $val;
145
                } elseif ($val instanceof \DateTimeInterface) {
146
                    $replacements["{{$key}}"] = $val->format(\DateTimeInterface::RFC3339);
147
                } elseif (\is_object($val)) {
148
                    $replacements["{{$key}}"] = '[object '.$val::class.']';
149
                } else {
150
                    $replacements["{{$key}}"] = '['.\gettype($val).']';
151
                }
152
            }
153
 
154
            $message = strtr($message, $replacements);
155
        }
156
 
157
        $log = sprintf('[%s] %s', $level, $message);
158
        if ($prefixDate) {
159
            $log = date(\DateTimeInterface::RFC3339).' '.$log;
160
        }
161
 
162
        return $log;
163
    }
164
 
165
    private function record($level, $message, array $context): void
166
    {
167
        $request = $this->requestStack->getCurrentRequest();
168
        $key = $request ? spl_object_id($request) : '';
169
 
170
        $this->logs[$key][] = [
171
            'channel' => null,
172
            'context' => $context,
173
            'message' => $message,
174
            'priority' => self::PRIORITIES[$level],
175
            'priorityName' => $level,
176
            'timestamp' => time(),
177
            'timestamp_rfc3339' => date(\DATE_RFC3339_EXTENDED),
178
        ];
179
 
180
        $this->errorCount[$key] ??= 0;
181
        switch ($level) {
182
            case LogLevel::ERROR:
183
            case LogLevel::CRITICAL:
184
            case LogLevel::ALERT:
185
            case LogLevel::EMERGENCY:
186
                ++$this->errorCount[$key];
187
        }
188
    }
189
}