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) . ');';
|
||
|
}
|
||
|
}
|