scratch – Blame information for rev 115

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