241 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
		
		
			
		
	
	
			241 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
|  | <?php | ||
|  | 
 | ||
|  | /* | ||
|  |  * This file is part of the Monolog package. | ||
|  |  * | ||
|  |  * (c) Jordi Boggiano <j.boggiano@seld.be> | ||
|  |  * | ||
|  |  * For the full copyright and license information, please view the LICENSE | ||
|  |  * file that was distributed with this source code. | ||
|  |  */ | ||
|  | 
 | ||
|  | namespace Monolog\Handler; | ||
|  | 
 | ||
|  | use Monolog\Formatter\LineFormatter; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Handler sending logs to browser's javascript console with no browser extension required | ||
|  |  * | ||
|  |  * @author Olivier Poitrey <rs@dailymotion.com> | ||
|  |  */ | ||
|  | class BrowserConsoleHandler extends AbstractProcessingHandler | ||
|  | { | ||
|  |     protected static $initialized = false; | ||
|  |     protected static $records = array(); | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritDoc} | ||
|  |      * | ||
|  |      * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format. | ||
|  |      * | ||
|  |      * Example of formatted string: | ||
|  |      * | ||
|  |      *     You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white} | ||
|  |      */ | ||
|  |     protected function getDefaultFormatter() | ||
|  |     { | ||
|  |         return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%'); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * {@inheritDoc} | ||
|  |      */ | ||
|  |     protected function write(array $record) | ||
|  |     { | ||
|  |         // Accumulate records
 | ||
|  |         static::$records[] = $record; | ||
|  | 
 | ||
|  |         // Register shutdown handler if not already done
 | ||
|  |         if (!static::$initialized) { | ||
|  |             static::$initialized = true; | ||
|  |             $this->registerShutdownFunction(); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Convert records to javascript console commands and send it to the browser. | ||
|  |      * This method is automatically called on PHP shutdown if output is HTML or Javascript. | ||
|  |      */ | ||
|  |     public static function send() | ||
|  |     { | ||
|  |         $format = static::getResponseFormat(); | ||
|  |         if ($format === 'unknown') { | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         if (count(static::$records)) { | ||
|  |             if ($format === 'html') { | ||
|  |                 static::writeOutput('<script>' . static::generateScript() . '</script>'); | ||
|  |             } elseif ($format === 'js') { | ||
|  |                 static::writeOutput(static::generateScript()); | ||
|  |             } | ||
|  |             static::resetStatic(); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     public function close() | ||
|  |     { | ||
|  |         self::resetStatic(); | ||
|  |     } | ||
|  | 
 | ||
|  |     public function reset() | ||
|  |     { | ||
|  |         self::resetStatic(); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Forget all logged records | ||
|  |      */ | ||
|  |     public static function resetStatic() | ||
|  |     { | ||
|  |         static::$records = array(); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Wrapper for register_shutdown_function to allow overriding | ||
|  |      */ | ||
|  |     protected function registerShutdownFunction() | ||
|  |     { | ||
|  |         if (PHP_SAPI !== 'cli') { | ||
|  |             register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send')); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Wrapper for echo to allow overriding | ||
|  |      * | ||
|  |      * @param string $str | ||
|  |      */ | ||
|  |     protected static function writeOutput($str) | ||
|  |     { | ||
|  |         echo $str; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Checks the format of the response | ||
|  |      * | ||
|  |      * If Content-Type is set to application/javascript or text/javascript -> js | ||
|  |      * If Content-Type is set to text/html, or is unset -> html | ||
|  |      * If Content-Type is anything else -> unknown | ||
|  |      * | ||
|  |      * @return string One of 'js', 'html' or 'unknown' | ||
|  |      */ | ||
|  |     protected static function getResponseFormat() | ||
|  |     { | ||
|  |         // Check content type
 | ||
|  |         foreach (headers_list() as $header) { | ||
|  |             if (stripos($header, 'content-type:') === 0) { | ||
|  |                 // This handler only works with HTML and javascript outputs
 | ||
|  |                 // text/javascript is obsolete in favour of application/javascript, but still used
 | ||
|  |                 if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) { | ||
|  |                     return 'js'; | ||
|  |                 } | ||
|  |                 if (stripos($header, 'text/html') === false) { | ||
|  |                     return 'unknown'; | ||
|  |                 } | ||
|  |                 break; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return 'html'; | ||
|  |     } | ||
|  | 
 | ||
|  |     private static function generateScript() | ||
|  |     { | ||
|  |         $script = array(); | ||
|  |         foreach (static::$records as $record) { | ||
|  |             $context = static::dump('Context', $record['context']); | ||
|  |             $extra = static::dump('Extra', $record['extra']); | ||
|  | 
 | ||
|  |             if (empty($context) && empty($extra)) { | ||
|  |                 $script[] = static::call_array('log', static::handleStyles($record['formatted'])); | ||
|  |             } else { | ||
|  |                 $script = array_merge($script, | ||
|  |                     array(static::call_array('groupCollapsed', static::handleStyles($record['formatted']))), | ||
|  |                     $context, | ||
|  |                     $extra, | ||
|  |                     array(static::call('groupEnd')) | ||
|  |                 ); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);"; | ||
|  |     } | ||
|  | 
 | ||
|  |     private static function handleStyles($formatted) | ||
|  |     { | ||
|  |         $args = array(static::quote('font-weight: normal')); | ||
|  |         $format = '%c' . $formatted; | ||
|  |         preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); | ||
|  | 
 | ||
|  |         foreach (array_reverse($matches) as $match) { | ||
|  |             $args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0])); | ||
|  |             $args[] = '"font-weight: normal"'; | ||
|  | 
 | ||
|  |             $pos = $match[0][1]; | ||
|  |             $format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0])); | ||
|  |         } | ||
|  | 
 | ||
|  |         array_unshift($args, static::quote($format)); | ||
|  | 
 | ||
|  |         return $args; | ||
|  |     } | ||
|  | 
 | ||
|  |     private static function handleCustomStyles($style, $string) | ||
|  |     { | ||
|  |         static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'); | ||
|  |         static $labels = array(); | ||
|  | 
 | ||
|  |         return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) { | ||
|  |             if (trim($m[1]) === 'autolabel') { | ||
|  |                 // Format the string as a label with consistent auto assigned background color
 | ||
|  |                 if (!isset($labels[$string])) { | ||
|  |                     $labels[$string] = $colors[count($labels) % count($colors)]; | ||
|  |                 } | ||
|  |                 $color = $labels[$string]; | ||
|  | 
 | ||
|  |                 return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px"; | ||
|  |             } | ||
|  | 
 | ||
|  |             return $m[1]; | ||
|  |         }, $style); | ||
|  |     } | ||
|  | 
 | ||
|  |     private static function dump($title, array $dict) | ||
|  |     { | ||
|  |         $script = array(); | ||
|  |         $dict = array_filter($dict); | ||
|  |         if (empty($dict)) { | ||
|  |             return $script; | ||
|  |         } | ||
|  |         $script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title)); | ||
|  |         foreach ($dict as $key => $value) { | ||
|  |             $value = json_encode($value); | ||
|  |             if (empty($value)) { | ||
|  |                 $value = static::quote(''); | ||
|  |             } | ||
|  |             $script[] = static::call('log', static::quote('%s: %o'), static::quote($key), $value); | ||
|  |         } | ||
|  | 
 | ||
|  |         return $script; | ||
|  |     } | ||
|  | 
 | ||
|  |     private static function quote($arg) | ||
|  |     { | ||
|  |         return '"' . addcslashes($arg, "\"\n\\") . '"'; | ||
|  |     } | ||
|  | 
 | ||
|  |     private static function call() | ||
|  |     { | ||
|  |         $args = func_get_args(); | ||
|  |         $method = array_shift($args); | ||
|  | 
 | ||
|  |         return static::call_array($method, $args); | ||
|  |     } | ||
|  | 
 | ||
|  |     private static function call_array($method, array $args) | ||
|  |     { | ||
|  |         return 'c.' . $method . '(' . implode(', ', $args) . ');'; | ||
|  |     } | ||
|  | } |