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\Handler;
13
 
14
use Monolog\Formatter\FormatterInterface;
15
use Monolog\Formatter\LineFormatter;
16
use Monolog\Utils;
17
 
18
use function count;
19
use function headers_list;
20
use function stripos;
21
use function trigger_error;
22
 
23
use const E_USER_DEPRECATED;
24
 
25
/**
26
 * Handler sending logs to browser's javascript console with no browser extension required
27
 *
28
 * @author Olivier Poitrey <rs@dailymotion.com>
29
 *
30
 * @phpstan-import-type FormattedRecord from AbstractProcessingHandler
31
 */
32
class BrowserConsoleHandler extends AbstractProcessingHandler
33
{
34
    /** @var bool */
35
    protected static $initialized = false;
36
    /** @var FormattedRecord[] */
37
    protected static $records = [];
38
 
39
    protected const FORMAT_HTML = 'html';
40
    protected const FORMAT_JS = 'js';
41
    protected const FORMAT_UNKNOWN = 'unknown';
42
 
43
    /**
44
     * {@inheritDoc}
45
     *
46
     * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format.
47
     *
48
     * Example of formatted string:
49
     *
50
     *     You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white}
51
     */
52
    protected function getDefaultFormatter(): FormatterInterface
53
    {
54
        return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%');
55
    }
56
 
57
    /**
58
     * {@inheritDoc}
59
     */
60
    protected function write(array $record): void
61
    {
62
        // Accumulate records
63
        static::$records[] = $record;
64
 
65
        // Register shutdown handler if not already done
66
        if (!static::$initialized) {
67
            static::$initialized = true;
68
            $this->registerShutdownFunction();
69
        }
70
    }
71
 
72
    /**
73
     * Convert records to javascript console commands and send it to the browser.
74
     * This method is automatically called on PHP shutdown if output is HTML or Javascript.
75
     */
76
    public static function send(): void
77
    {
78
        $format = static::getResponseFormat();
79
        if ($format === self::FORMAT_UNKNOWN) {
80
            return;
81
        }
82
 
83
        if (count(static::$records)) {
84
            if ($format === self::FORMAT_HTML) {
85
                static::writeOutput('<script>' . static::generateScript() . '</script>');
86
            } elseif ($format === self::FORMAT_JS) {
87
                static::writeOutput(static::generateScript());
88
            }
89
            static::resetStatic();
90
        }
91
    }
92
 
93
    public function close(): void
94
    {
95
        self::resetStatic();
96
    }
97
 
98
    public function reset()
99
    {
100
        parent::reset();
101
 
102
        self::resetStatic();
103
    }
104
 
105
    /**
106
     * Forget all logged records
107
     */
108
    public static function resetStatic(): void
109
    {
110
        static::$records = [];
111
    }
112
 
113
    /**
114
     * Wrapper for register_shutdown_function to allow overriding
115
     */
116
    protected function registerShutdownFunction(): void
117
    {
118
        if (PHP_SAPI !== 'cli') {
119
            register_shutdown_function(['Monolog\Handler\BrowserConsoleHandler', 'send']);
120
        }
121
    }
122
 
123
    /**
124
     * Wrapper for echo to allow overriding
125
     */
126
    protected static function writeOutput(string $str): void
127
    {
128
        echo $str;
129
    }
130
 
131
    /**
132
     * Checks the format of the response
133
     *
134
     * If Content-Type is set to application/javascript or text/javascript -> js
135
     * If Content-Type is set to text/html, or is unset -> html
136
     * If Content-Type is anything else -> unknown
137
     *
138
     * @return string One of 'js', 'html' or 'unknown'
139
     * @phpstan-return self::FORMAT_*
140
     */
141
    protected static function getResponseFormat(): string
142
    {
143
        // Check content type
144
        foreach (headers_list() as $header) {
145
            if (stripos($header, 'content-type:') === 0) {
146
                return static::getResponseFormatFromContentType($header);
147
            }
148
        }
149
 
150
        return self::FORMAT_HTML;
151
    }
152
 
153
    /**
154
     * @return string One of 'js', 'html' or 'unknown'
155
     * @phpstan-return self::FORMAT_*
156
     */
157
    protected static function getResponseFormatFromContentType(string $contentType): string
158
    {
159
        // This handler only works with HTML and javascript outputs
160
        // text/javascript is obsolete in favour of application/javascript, but still used
161
        if (stripos($contentType, 'application/javascript') !== false || stripos($contentType, 'text/javascript') !== false) {
162
            return self::FORMAT_JS;
163
        }
164
 
165
        if (stripos($contentType, 'text/html') !== false) {
166
            return self::FORMAT_HTML;
167
        }
168
 
169
        return self::FORMAT_UNKNOWN;
170
    }
171
 
172
    private static function generateScript(): string
173
    {
174
        $script = [];
175
        foreach (static::$records as $record) {
176
            $context = static::dump('Context', $record['context']);
177
            $extra = static::dump('Extra', $record['extra']);
178
 
179
            if (empty($context) && empty($extra)) {
180
                $script[] = static::call_array('log', static::handleStyles($record['formatted']));
181
            } else {
182
                $script = array_merge(
183
                    $script,
184
                    [static::call_array('groupCollapsed', static::handleStyles($record['formatted']))],
185
                    $context,
186
                    $extra,
187
                    [static::call('groupEnd')]
188
                );
189
            }
190
        }
191
 
192
        return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);";
193
    }
194
 
195
    /**
196
     * @return string[]
197
     */
198
    private static function handleStyles(string $formatted): array
199
    {
200
        $args = [];
201
        $format = '%c' . $formatted;
202
        preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
203
 
204
        foreach (array_reverse($matches) as $match) {
205
            $args[] = '"font-weight: normal"';
206
            $args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0]));
207
 
208
            $pos = $match[0][1];
209
            $format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + strlen($match[0][0]));
210
        }
211
 
212
        $args[] = static::quote('font-weight: normal');
213
        $args[] = static::quote($format);
214
 
215
        return array_reverse($args);
216
    }
217
 
218
    private static function handleCustomStyles(string $style, string $string): string
219
    {
220
        static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'];
221
        static $labels = [];
222
 
223
        $style = preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) {
224
            if (trim($m[1]) === 'autolabel') {
225
                // Format the string as a label with consistent auto assigned background color
226
                if (!isset($labels[$string])) {
227
                    $labels[$string] = $colors[count($labels) % count($colors)];
228
                }
229
                $color = $labels[$string];
230
 
231
                return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px";
232
            }
233
 
234
            return $m[1];
235
        }, $style);
236
 
237
        if (null === $style) {
238
            $pcreErrorCode = preg_last_error();
239
            throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . Utils::pcreLastErrorMessage($pcreErrorCode));
240
        }
241
 
242
        return $style;
243
    }
244
 
245
    /**
246
     * @param  mixed[] $dict
247
     * @return mixed[]
248
     */
249
    private static function dump(string $title, array $dict): array
250
    {
251
        $script = [];
252
        $dict = array_filter($dict);
253
        if (empty($dict)) {
254
            return $script;
255
        }
256
        $script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title));
257
        foreach ($dict as $key => $value) {
258
            $value = json_encode($value);
259
            if (empty($value)) {
260
                $value = static::quote('');
261
            }
262
            $script[] = static::call('log', static::quote('%s: %o'), static::quote((string) $key), $value);
263
        }
264
 
265
        return $script;
266
    }
267
 
268
    private static function quote(string $arg): string
269
    {
270
        return '"' . addcslashes($arg, "\"\n\\") . '"';
271
    }
272
 
273
    /**
274
     * @param mixed $args
275
     */
276
    private static function call(...$args): string
277
    {
278
        $method = array_shift($args);
279
        if (!is_string($method)) {
280
            throw new \UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true));
281
        }
282
 
283
        return static::call_array($method, $args);
284
    }
285
 
286
    /**
287
     * @param mixed[] $args
288
     */
289
    private static function call_array(string $method, array $args): string
290
    {
291
        return 'c.' . $method . '(' . implode(', ', $args) . ');';
292
    }
293
}